8046321: OCSP Stapling for TLS

Initial feature commit for OCSP stapling in JSSE

Reviewed-by: xuelei, mullan
This commit is contained in:
Jamil Nimeh 2015-08-05 12:19:38 -07:00
parent 16655aecb0
commit e7f31340a0
45 changed files with 10998 additions and 191 deletions

View file

@ -115,4 +115,45 @@ public abstract class ExtendedSSLSession implements SSLSession {
public List<SNIServerName> getRequestedServerNames() { public List<SNIServerName> getRequestedServerNames() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* Returns a {@link List} containing DER-encoded OCSP responses
* (using the ASN.1 type OCSPResponse defined in RFC 6960) for
* the client to verify status of the server's certificate during
* handshaking.
*
* <P>
* This method only applies to certificate-based server
* authentication. An {@link X509ExtendedTrustManager} will use the
* returned value for server certificate validation.
*
* @implSpec This method throws UnsupportedOperationException by default.
* Classes derived from ExtendedSSLSession must implement
* this method.
*
* @return a non-null unmodifiable list of byte arrays, each entry
* containing a DER-encoded OCSP response (using the
* ASN.1 type OCSPResponse defined in RFC 6960). The order
* of the responses must match the order of the certificates
* presented by the server in its Certificate message (See
* {@link SSLSession#getLocalCertificates()} for server mode,
* and {@link SSLSession#getPeerCertificates()} for client mode).
* It is possible that fewer response entries may be returned than
* the number of presented certificates. If an entry in the list
* is a zero-length byte array, it should be treated by the
* caller as if the OCSP entry for the corresponding certificate
* is missing. The returned list may be empty if no OCSP responses
* were presented during handshaking or if OCSP stapling is not
* supported by either endpoint for this handshake.
*
* @throws UnsupportedOperationException if the underlying provider
* does not implement the operation
*
* @see X509ExtendedTrustManager
*
* @since 9
*/
public List<byte[]> getStatusResponses() {
throw new UnsupportedOperationException();
}
} }

View file

@ -42,14 +42,13 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static sun.security.provider.certpath.OCSPResponse.*;
import sun.security.action.GetIntegerAction; import sun.security.action.GetIntegerAction;
import sun.security.util.Debug; import sun.security.util.Debug;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AccessDescription; import sun.security.x509.AccessDescription;
import sun.security.x509.AuthorityInfoAccessExtension; import sun.security.x509.AuthorityInfoAccessExtension;
import sun.security.x509.GeneralName; import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNameInterface; import sun.security.x509.GeneralNameInterface;
import sun.security.x509.PKIXExtensions;
import sun.security.x509.URIName; import sun.security.x509.URIName;
import sun.security.x509.X509CertImpl; import sun.security.x509.X509CertImpl;
@ -65,9 +64,6 @@ import sun.security.x509.X509CertImpl;
*/ */
public final class OCSP { public final class OCSP {
static final ObjectIdentifier NONCE_EXTENSION_OID =
ObjectIdentifier.newInternal(new int[]{ 1, 3, 6, 1, 5, 5, 7, 48, 1, 2});
private static final Debug debug = Debug.getInstance("certpath"); private static final Debug debug = Debug.getInstance("certpath");
private static final int DEFAULT_CONNECT_TIMEOUT = 15000; private static final int DEFAULT_CONNECT_TIMEOUT = 15000;
@ -184,12 +180,15 @@ public final class OCSP {
/** /**
* Checks the revocation status of a list of certificates using OCSP. * Checks the revocation status of a list of certificates using OCSP.
* *
* @param certs the CertIds to be checked * @param certIds the CertIds to be checked
* @param responderURI the URI of the OCSP responder * @param responderURI the URI of the OCSP responder
* @param issuerCert the issuer's certificate * @param issuerCert the issuer's certificate
* @param responderCert the OCSP responder's certificate * @param responderCert the OCSP responder's certificate
* @param date the time the validity of the OCSP responder's certificate * @param date the time the validity of the OCSP responder's certificate
* should be checked against. If null, the current time is used. * should be checked against. If null, the current time is used.
* @param extensions zero or more OCSP extensions to be included in the
* request. If no extensions are requested, an empty {@code List} must
* be used. A {@code null} value is not allowed.
* @return the OCSPResponse * @return the OCSPResponse
* @throws IOException if there is an exception connecting to or * @throws IOException if there is an exception connecting to or
* communicating with the OCSP responder * communicating with the OCSP responder
@ -202,19 +201,54 @@ public final class OCSP {
List<Extension> extensions) List<Extension> extensions)
throws IOException, CertPathValidatorException throws IOException, CertPathValidatorException
{ {
byte[] bytes = null; byte[] nonce = null;
OCSPRequest request = null; for (Extension ext : extensions) {
try { if (ext.getId().equals(PKIXExtensions.OCSPNonce_Id.toString())) {
request = new OCSPRequest(certIds, extensions); nonce = ext.getValue();
bytes = request.encodeBytes();
} catch (IOException ioe) {
throw new CertPathValidatorException
("Exception while encoding OCSPRequest", ioe);
} }
}
OCSPResponse ocspResponse = null;
try {
byte[] response = getOCSPBytes(certIds, responderURI, extensions);
ocspResponse = new OCSPResponse(response);
// verify the response
ocspResponse.verify(certIds, issuerCert, responderCert, date,
nonce);
} catch (IOException ioe) {
throw new CertPathValidatorException(
"Unable to determine revocation status due to network error",
ioe, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
}
return ocspResponse;
}
/**
* Send an OCSP request, then read and return the OCSP response bytes.
*
* @param certIds the CertIds to be checked
* @param responderURI the URI of the OCSP responder
* @param extensions zero or more OCSP extensions to be included in the
* request. If no extensions are requested, an empty {@code List} must
* be used. A {@code null} value is not allowed.
*
* @return the OCSP response bytes
*
* @throws IOException if there is an exception connecting to or
* communicating with the OCSP responder
*/
public static byte[] getOCSPBytes(List<CertId> certIds, URI responderURI,
List<Extension> extensions) throws IOException {
OCSPRequest request = new OCSPRequest(certIds, extensions);
byte[] bytes = request.encodeBytes();
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
byte[] response = null; byte[] response = null;
try { try {
URL url = responderURI.toURL(); URL url = responderURI.toURL();
if (debug != null) { if (debug != null) {
@ -257,10 +291,6 @@ public final class OCSP {
} }
} }
response = Arrays.copyOf(response, total); response = Arrays.copyOf(response, total);
} catch (IOException ioe) {
throw new CertPathValidatorException(
"Unable to determine revocation status due to network error",
ioe, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
} finally { } finally {
if (in != null) { if (in != null) {
try { try {
@ -277,20 +307,7 @@ public final class OCSP {
} }
} }
} }
return response;
OCSPResponse ocspResponse = null;
try {
ocspResponse = new OCSPResponse(response);
} catch (IOException ioe) {
// response decoding exception
throw new CertPathValidatorException(ioe);
}
// verify the response
ocspResponse.verify(certIds, issuerCert, responderCert, date,
request.getNonce());
return ocspResponse;
} }
/** /**

View file

@ -0,0 +1,294 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider.certpath;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.security.SecureRandom;
import sun.security.x509.AttributeNameEnumeration;
import sun.security.x509.CertAttrSet;
import sun.security.x509.Extension;
import sun.security.x509.PKIXExtensions;
import sun.security.util.*;
/**
* Represent the OCSP Nonce Extension.
* This extension, if present, provides a nonce value in OCSP requests
* and responses. This will cryptographically bind requests and responses
* and help to prevent replay attacks (see RFC 6960, section 4.4.1).
*
* @see Extension
* @see CertAttrSet
*/
public class OCSPNonceExtension extends Extension
implements CertAttrSet<String> {
/**
* Attribute name.
*/
public static final String NAME = "OCSPNonce";
public static final String NONCE = "nonce";
private byte[] nonceData = null;
private String extensionName;
/**
* Encode this extension value to DER and assign it to the
* {@code extensionName} data member.
*
* @throws IOException if any errors occur during DER encoding
*/
private void encodeInternal() throws IOException {
if (nonceData == null) {
this.extensionValue = null;
return;
}
DerOutputStream os = new DerOutputStream();
os.putOctetString(this.nonceData);
this.extensionValue = os.toByteArray();
}
/**
* Create a {@code OCSPNonceExtension} by providing the nonce length.
* The criticality is set to false. The random bytes will be generated
* using the SUN provider.
*
* @param length the number of random bytes composing the nonce
*
* @throws IOException if any errors happen during encoding of the
* extension.
*/
public OCSPNonceExtension(int length) throws IOException {
this(PKIXExtensions.OCSPNonce_Id, false, length, NAME);
}
/**
* Creates the extension (also called by the subclass).
*
* @param extensionId the {@code ObjectIdentifier} for the OCSP Nonce
* extension
* @param isCritical a boolean flag indicating if the criticality bit
* is to be set for this extension
* @param length the length of the nonce in bytes
* @param extensionName the name of the extension
*
* @throws IOException if any errors happen during encoding of the
* extension.
*/
protected OCSPNonceExtension(ObjectIdentifier extensionId,
boolean isCritical, int length, String extensionName)
throws IOException {
SecureRandom rng = new SecureRandom();
this.nonceData = new byte[length];
rng.nextBytes(nonceData);
this.extensionId = extensionId;
this.critical = isCritical;
this.extensionName = extensionName;
encodeInternal();
}
/**
* Create the extension using the provided criticality bit setting and
* DER encoding.
*
* @param critical true if the extension is to be treated as critical.
* @param value an array of DER encoded bytes of the extnValue for the
* extension. It must not include the encapsulating OCTET STRING
* tag and length. For an {@code OCSPNonceExtension} the data value
* should be a simple OCTET STRING containing random bytes
* (see RFC 6960, section 4.4.1).
*
* @throws ClassCastException if value is not an array of bytes
* @throws IOException if any errors happen during encoding of the
* extension
*/
public OCSPNonceExtension(Boolean critical, Object value)
throws IOException {
this(PKIXExtensions.OCSPNonce_Id, critical, value, NAME);
}
/**
* Creates the extension (also called by the subclass).
*
* @param extensionId the {@code ObjectIdentifier} for the OCSP Nonce
* extension
* @param critical a boolean flag indicating if the criticality bit
* is to be set for this extension
* @param value an array of DER encoded bytes of the extnValue for the
* extension. It must not include the encapsulating OCTET STRING
* tag and length. For an {@code OCSPNonceExtension} the data value
* should be a simple OCTET STRING containing random bytes
* (see RFC 6960, section 4.4.1).
* @param extensionName the name of the extension
*
* @throws ClassCastException if value is not an array of bytes
* @throws IOException if any errors happen during encoding of the
* extension
*/
protected OCSPNonceExtension(ObjectIdentifier extensionId,
Boolean critical, Object value, String extensionName)
throws IOException {
this.extensionId = extensionId;
this.critical = critical;
this.extensionValue = (byte[]) value;
DerValue val = new DerValue(this.extensionValue);
this.nonceData = val.getOctetString();
this.extensionName = extensionName;
}
/**
* Set the attribute value.
*
* @param name the name of the attribute.
* @param obj an array of nonce bytes for the extension. It must not
* contain any DER tags or length.
*
* @throws IOException if an unsupported name is provided or the supplied
* {@code obj} is not a byte array
*/
@Override
public void set(String name, Object obj) throws IOException {
if (name.equalsIgnoreCase(NONCE)) {
if (!(obj instanceof byte[])) {
throw new IOException("Attribute must be of type byte[].");
}
nonceData = (byte[])obj;
} else {
throw new IOException("Attribute name not recognized by"
+ " CertAttrSet:" + extensionName + ".");
}
encodeInternal();
}
/**
* Get the attribute value.
*
* @param name the name of the attribute to retrieve. Only "OCSPNonce"
* is currently supported.
*
* @return an array of bytes that are the nonce data. It will not contain
* any DER tags or length, only the random nonce bytes.
*
* @throws IOException if an unsupported name is provided.
*/
@Override
public Object get(String name) throws IOException {
if (name.equalsIgnoreCase(NONCE)) {
return nonceData;
} else {
throw new IOException("Attribute name not recognized by"
+ " CertAttrSet:" + extensionName + ".");
}
}
/**
* Delete the attribute value.
*
* @param name the name of the attribute to retrieve. Only "OCSPNonce"
* is currently supported.
*
* @throws IOException if an unsupported name is provided or an error
* occurs during re-encoding of the extension.
*/
@Override
public void delete(String name) throws IOException {
if (name.equalsIgnoreCase(NONCE)) {
nonceData = null;
} else {
throw new IOException("Attribute name not recognized by"
+ " CertAttrSet:" + extensionName + ".");
}
encodeInternal();
}
/**
* Returns a printable representation of the {@code OCSPNonceExtension}.
*/
@Override
public String toString() {
String s = super.toString() + extensionName + ": " +
((nonceData == null) ? "" : Debug.toString(nonceData))
+ "\n";
return (s);
}
/**
* Write the extension to an {@code OutputStream}
*
* @param out the {@code OutputStream} to write the extension to.
*
* @throws IOException on encoding errors.
*/
@Override
public void encode(OutputStream out) throws IOException {
encode(out, PKIXExtensions.OCSPNonce_Id, this.critical);
}
/**
* Write the extension to the DerOutputStream.
*
* @param out the {@code OutputStream} to write the extension to.
* @param extensionId the {@code ObjectIdentifier} used for this extension
* @param isCritical a flag indicating if the criticality bit is set for
* this extension.
*
* @throws IOException on encoding errors.
*/
protected void encode(OutputStream out, ObjectIdentifier extensionId,
boolean isCritical) throws IOException {
DerOutputStream tmp = new DerOutputStream();
if (this.extensionValue == null) {
this.extensionId = extensionId;
this.critical = isCritical;
encodeInternal();
}
super.encode(tmp);
out.write(tmp.toByteArray());
}
/**
* Return an enumeration of names of attributes existing within this
* attribute.
*/
@Override
public Enumeration<String> getElements() {
AttributeNameEnumeration elements = new AttributeNameEnumeration();
elements.addElement(NONCE);
return (elements.elements());
}
/**
* Return the name of this attribute.
*/
@Override
public String getName() {
return (extensionName);
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -32,10 +32,11 @@ import java.util.List;
import sun.misc.HexDumpEncoder; import sun.misc.HexDumpEncoder;
import sun.security.util.*; import sun.security.util.*;
import sun.security.x509.PKIXExtensions;
/** /**
* This class can be used to generate an OCSP request and send it over * This class can be used to generate an OCSP request and send it over
* an outputstream. Currently we do not support signing requests * an output stream. Currently we do not support signing requests.
* The OCSP Request is specified in RFC 2560 and * The OCSP Request is specified in RFC 2560 and
* the ASN.1 definition is as follows: * the ASN.1 definition is as follows:
* <pre> * <pre>
@ -118,7 +119,8 @@ class OCSPRequest {
DerOutputStream extOut = new DerOutputStream(); DerOutputStream extOut = new DerOutputStream();
for (Extension ext : extensions) { for (Extension ext : extensions) {
ext.encode(extOut); ext.encode(extOut);
if (ext.getId().equals(OCSP.NONCE_EXTENSION_OID.toString())) { if (ext.getId().equals(
PKIXExtensions.OCSPNonce_Id.toString())) {
nonce = ext.getValue(); nonce = ext.getValue();
} }
} }

View file

@ -41,6 +41,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import sun.misc.HexDumpEncoder; import sun.misc.HexDumpEncoder;
@ -129,7 +130,7 @@ public final class OCSPResponse {
SIG_REQUIRED, // Must sign the request SIG_REQUIRED, // Must sign the request
UNAUTHORIZED // Request unauthorized UNAUTHORIZED // Request unauthorized
}; };
private static ResponseStatus[] rsvalues = ResponseStatus.values(); private static final ResponseStatus[] rsvalues = ResponseStatus.values();
private static final Debug debug = Debug.getInstance("certpath"); private static final Debug debug = Debug.getInstance("certpath");
private static final boolean dump = debug != null && Debug.isOn("ocsp"); private static final boolean dump = debug != null && Debug.isOn("ocsp");
@ -173,7 +174,7 @@ public final class OCSPResponse {
} }
// an array of all of the CRLReasons (used in SingleResponse) // an array of all of the CRLReasons (used in SingleResponse)
private static CRLReason[] values = CRLReason.values(); private static final CRLReason[] values = CRLReason.values();
private final ResponseStatus responseStatus; private final ResponseStatus responseStatus;
private final Map<CertId, SingleResponse> singleResponseMap; private final Map<CertId, SingleResponse> singleResponseMap;
@ -183,13 +184,16 @@ public final class OCSPResponse {
private final byte[] responseNonce; private final byte[] responseNonce;
private List<X509CertImpl> certs; private List<X509CertImpl> certs;
private X509CertImpl signerCert = null; private X509CertImpl signerCert = null;
private X500Principal responderName = null; private final ResponderId respId;
private KeyIdentifier responderKeyId = null; private Date producedAtDate = null;
private final Map<String, java.security.cert.Extension> responseExtensions;
/* /*
* Create an OCSP response from its ASN.1 DER encoding. * Create an OCSP response from its ASN.1 DER encoding.
*
* @param bytes The DER-encoded bytes for an OCSP response
*/ */
OCSPResponse(byte[] bytes) throws IOException { public OCSPResponse(byte[] bytes) throws IOException {
if (dump) { if (dump) {
HexDumpEncoder hexEnc = new HexDumpEncoder(); HexDumpEncoder hexEnc = new HexDumpEncoder();
debug.println("OCSPResponse bytes...\n\n" + debug.println("OCSPResponse bytes...\n\n" +
@ -221,6 +225,8 @@ public final class OCSPResponse {
signature = null; signature = null;
tbsResponseData = null; tbsResponseData = null;
responseNonce = null; responseNonce = null;
responseExtensions = Collections.emptyMap();
respId = null;
return; return;
} }
@ -239,7 +245,7 @@ public final class OCSPResponse {
// responseType // responseType
derIn = tmp.data; derIn = tmp.data;
ObjectIdentifier responseType = derIn.getOID(); ObjectIdentifier responseType = derIn.getOID();
if (responseType.equals(OCSP_BASIC_RESPONSE_OID)) { if (responseType.equals((Object)OCSP_BASIC_RESPONSE_OID)) {
if (debug != null) { if (debug != null) {
debug.println("OCSP response type: basic"); debug.println("OCSP response type: basic");
} }
@ -289,27 +295,15 @@ public final class OCSPResponse {
} }
// responderID // responderID
short tag = (byte)(seq.tag & 0x1f); respId = new ResponderId(seq.toByteArray());
if (tag == NAME_TAG) {
responderName = new X500Principal(seq.getData().toByteArray());
if (debug != null) { if (debug != null) {
debug.println("Responder's name: " + responderName); debug.println("Responder ID: " + respId);
}
} else if (tag == KEY_TAG) {
responderKeyId = new KeyIdentifier(seq.getData().getOctetString());
if (debug != null) {
debug.println("Responder's key ID: " +
Debug.toString(responderKeyId.getIdentifier()));
}
} else {
throw new IOException("Bad encoding in responderID element of " +
"OCSP response: expected ASN.1 context specific tag 0 or 1");
} }
// producedAt // producedAt
seq = seqDerIn.getDerValue(); seq = seqDerIn.getDerValue();
producedAtDate = seq.getGeneralizedTime();
if (debug != null) { if (debug != null) {
Date producedAtDate = seq.getGeneralizedTime();
debug.println("OCSP response produced at: " + producedAtDate); debug.println("OCSP response produced at: " + producedAtDate);
} }
@ -320,36 +314,29 @@ public final class OCSPResponse {
debug.println("OCSP number of SingleResponses: " debug.println("OCSP number of SingleResponses: "
+ singleResponseDer.length); + singleResponseDer.length);
} }
for (int i = 0; i < singleResponseDer.length; i++) { for (DerValue srDer : singleResponseDer) {
SingleResponse singleResponse = SingleResponse singleResponse = new SingleResponse(srDer);
new SingleResponse(singleResponseDer[i]);
singleResponseMap.put(singleResponse.getCertId(), singleResponse); singleResponseMap.put(singleResponse.getCertId(), singleResponse);
} }
// responseExtensions // responseExtensions
byte[] nonce = null; Map<String, java.security.cert.Extension> tmpExtMap = new HashMap<>();
if (seqDerIn.available() > 0) { if (seqDerIn.available() > 0) {
seq = seqDerIn.getDerValue(); seq = seqDerIn.getDerValue();
if (seq.isContextSpecific((byte)1)) { if (seq.isContextSpecific((byte)1)) {
DerValue[] responseExtDer = seq.data.getSequence(3); tmpExtMap = parseExtensions(seq);
for (int i = 0; i < responseExtDer.length; i++) {
Extension ext = new Extension(responseExtDer[i]);
if (debug != null) {
debug.println("OCSP extension: " + ext);
}
// Only the NONCE extension is recognized
if (ext.getExtensionId().equals(OCSP.NONCE_EXTENSION_OID))
{
nonce = ext.getExtensionValue();
} else if (ext.isCritical()) {
throw new IOException(
"Unsupported OCSP critical extension: " +
ext.getExtensionId());
} }
} }
responseExtensions = tmpExtMap;
// Attach the nonce value if found in the extension map
Extension nonceExt = (Extension)tmpExtMap.get(
PKIXExtensions.OCSPNonce_Id.toString());
responseNonce = (nonceExt != null) ?
nonceExt.getExtensionValue() : null;
if (debug != null && responseNonce != null) {
debug.println("Response nonce: " + Arrays.toString(responseNonce));
} }
}
responseNonce = nonce;
// signatureAlgorithmId // signatureAlgorithmId
sigAlgId = AlgorithmId.parse(seqTmp[1]); sigAlgId = AlgorithmId.parse(seqTmp[1]);
@ -436,20 +423,22 @@ public final class OCSPResponse {
"Invalid issuer or trusted responder certificate", ce); "Invalid issuer or trusted responder certificate", ce);
} }
if (responderName != null) { if (respId.getType() == ResponderId.Type.BY_NAME) {
X500Principal rName = respId.getResponderName();
for (X509CertImpl cert : certs) { for (X509CertImpl cert : certs) {
if (cert.getSubjectX500Principal().equals(responderName)) { if (cert.getSubjectX500Principal().equals(rName)) {
signerCert = cert; signerCert = cert;
break; break;
} }
} }
} else if (responderKeyId != null) { } else if (respId.getType() == ResponderId.Type.BY_KEY) {
KeyIdentifier ridKeyId = respId.getKeyIdentifier();
for (X509CertImpl cert : certs) { for (X509CertImpl cert : certs) {
// Match responder's key identifier against the cert's SKID // Match responder's key identifier against the cert's SKID
// This will match if the SKID is encoded using the 160-bit // This will match if the SKID is encoded using the 160-bit
// SHA-1 hash method as defined in RFC 5280. // SHA-1 hash method as defined in RFC 5280.
KeyIdentifier certKeyId = cert.getSubjectKeyId(); KeyIdentifier certKeyId = cert.getSubjectKeyId();
if (certKeyId != null && responderKeyId.equals(certKeyId)) { if (certKeyId != null && ridKeyId.equals(certKeyId)) {
signerCert = cert; signerCert = cert;
break; break;
} else { } else {
@ -463,7 +452,7 @@ public final class OCSPResponse {
} catch (IOException e) { } catch (IOException e) {
// ignore // ignore
} }
if (responderKeyId.equals(certKeyId)) { if (ridKeyId.equals(certKeyId)) {
signerCert = cert; signerCert = cert;
break; break;
} }
@ -592,7 +581,6 @@ public final class OCSPResponse {
} }
// Check freshness of OCSPResponse // Check freshness of OCSPResponse
long now = (date == null) ? System.currentTimeMillis() : date.getTime(); long now = (date == null) ? System.currentTimeMillis() : date.getTime();
Date nowPlusSkew = new Date(now + MAX_CLOCK_SKEW); Date nowPlusSkew = new Date(now + MAX_CLOCK_SKEW);
Date nowMinusSkew = new Date(now - MAX_CLOCK_SKEW); Date nowMinusSkew = new Date(now - MAX_CLOCK_SKEW);
@ -624,8 +612,10 @@ public final class OCSPResponse {
/** /**
* Returns the OCSP ResponseStatus. * Returns the OCSP ResponseStatus.
*
* @return the {@code ResponseStatus} for this OCSP response
*/ */
ResponseStatus getResponseStatus() { public ResponseStatus getResponseStatus() {
return responseStatus; return responseStatus;
} }
@ -663,11 +653,27 @@ public final class OCSPResponse {
/** /**
* Returns the SingleResponse of the specified CertId, or null if * Returns the SingleResponse of the specified CertId, or null if
* there is no response for that CertId. * there is no response for that CertId.
*
* @param certId the {@code CertId} for a {@code SingleResponse} to be
* searched for in the OCSP response.
*
* @return the {@code SingleResponse} for the provided {@code CertId},
* or {@code null} if it is not found.
*/ */
SingleResponse getSingleResponse(CertId certId) { public SingleResponse getSingleResponse(CertId certId) {
return singleResponseMap.get(certId); return singleResponseMap.get(certId);
} }
/**
* Return a set of all CertIds in this {@code OCSPResponse}
*
* @return an unmodifiable set containing every {@code CertId} in this
* response.
*/
public Set<CertId> getCertIds() {
return Collections.unmodifiableSet(singleResponseMap.keySet());
}
/* /*
* Returns the certificate for the authority that signed the OCSP response. * Returns the certificate for the authority that signed the OCSP response.
*/ */
@ -675,12 +681,53 @@ public final class OCSPResponse {
return signerCert; // set in verify() return signerCert; // set in verify()
} }
/**
* Get the {@code ResponderId} from this {@code OCSPResponse}
*
* @return the {@code ResponderId} from this response or {@code null}
* if no responder ID is in the body of the response (e.g. a
* response with a status other than SUCCESS.
*/
public ResponderId getResponderId() {
return respId;
}
/**
* Provide a String representation of an OCSPResponse
*
* @return a human-readable representation of the OCSPResponse
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("OCSP Response:\n");
sb.append("Response Status: ").append(responseStatus).append("\n");
sb.append("Responder ID: ").append(respId).append("\n");
sb.append("Produced at: ").append(producedAtDate).append("\n");
int count = singleResponseMap.size();
sb.append(count).append(count == 1 ?
" response:\n" : " responses:\n");
for (SingleResponse sr : singleResponseMap.values()) {
sb.append(sr).append("\n");
}
if (responseExtensions != null && responseExtensions.size() > 0) {
count = responseExtensions.size();
sb.append(count).append(count == 1 ?
" extension:\n" : " extensions:\n");
for (String extId : responseExtensions.keySet()) {
sb.append(responseExtensions.get(extId)).append("\n");
}
}
return sb.toString();
}
/** /**
* Build a String-Extension map from DER encoded data. * Build a String-Extension map from DER encoded data.
* @param derVal A {@code DerValue} object built from a SEQUENCE of * @param derVal A {@code DerValue} object built from a SEQUENCE of
* extensions * extensions
* *
* @return A {@code Map} using the OID in string form as the keys. If no * @return a {@code Map} using the OID in string form as the keys. If no
* extensions are found or an empty SEQUENCE is passed in, then * extensions are found or an empty SEQUENCE is passed in, then
* an empty {@code Map} will be returned. * an empty {@code Map} will be returned.
* *
@ -694,6 +741,9 @@ public final class OCSPResponse {
for (DerValue extDerVal : extDer) { for (DerValue extDerVal : extDer) {
Extension ext = new Extension(extDerVal); Extension ext = new Extension(extDerVal);
if (debug != null) {
debug.println("Extension: " + ext);
}
// We don't support any extensions yet. Therefore, if it // We don't support any extensions yet. Therefore, if it
// is critical we must throw an exception because we // is critical we must throw an exception because we
// don't know how to process it. // don't know how to process it.
@ -710,7 +760,7 @@ public final class OCSPResponse {
/* /*
* A class representing a single OCSP response. * A class representing a single OCSP response.
*/ */
final static class SingleResponse implements OCSP.RevocationStatus { public final static class SingleResponse implements OCSP.RevocationStatus {
private final CertId certId; private final CertId certId;
private final CertStatus certStatus; private final CertStatus certStatus;
private final Date thisUpdate; private final Date thisUpdate;
@ -825,23 +875,72 @@ public final class OCSPResponse {
/* /*
* Return the certificate's revocation status code * Return the certificate's revocation status code
*/ */
@Override public CertStatus getCertStatus() { @Override
public CertStatus getCertStatus() {
return certStatus; return certStatus;
} }
private CertId getCertId() { /**
* Get the Cert ID that this SingleResponse is for.
*
* @return the {@code CertId} for this {@code SingleResponse}
*/
public CertId getCertId() {
return certId; return certId;
} }
@Override public Date getRevocationTime() { /**
* Get the {@code thisUpdate} field from this {@code SingleResponse}.
*
* @return a {@link Date} object containing the thisUpdate date
*/
public Date getThisUpdate() {
return (thisUpdate != null ? (Date) thisUpdate.clone() : null);
}
/**
* Get the {@code nextUpdate} field from this {@code SingleResponse}.
*
* @return a {@link Date} object containing the nexUpdate date or
* {@code null} if a nextUpdate field is not present in the response.
*/
public Date getNextUpdate() {
return (nextUpdate != null ? (Date) nextUpdate.clone() : null);
}
/**
* Get the {@code revocationTime} field from this
* {@code SingleResponse}.
*
* @return a {@link Date} object containing the revocationTime date or
* {@code null} if the {@code SingleResponse} does not have a status
* of {@code REVOKED}.
*/
@Override
public Date getRevocationTime() {
return (revocationTime != null ? (Date) revocationTime.clone() : return (revocationTime != null ? (Date) revocationTime.clone() :
null); null);
} }
@Override public CRLReason getRevocationReason() { /**
* Get the {@code revocationReason} field for the
* {@code SingleResponse}.
*
* @return a {@link CRLReason} containing the revocation reason, or
* {@code null} if a revocation reason was not provided or the
* response status is not {@code REVOKED}.
*/
@Override
public CRLReason getRevocationReason() {
return revocationReason; return revocationReason;
} }
/**
* Get the {@code singleExtensions} for this {@code SingleResponse}.
*
* @return a {@link Map} of {@link Extension} objects, keyed by
* their OID value in string form.
*/
@Override @Override
public Map<String, java.security.cert.Extension> getSingleExtensions() { public Map<String, java.security.cert.Extension> getSingleExtensions() {
return Collections.unmodifiableMap(singleExtensions); return Collections.unmodifiableMap(singleExtensions);
@ -854,17 +953,20 @@ public final class OCSPResponse {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("SingleResponse:\n"); sb.append("SingleResponse:\n");
sb.append(certId); sb.append(certId);
sb.append("\nCertStatus: "+ certStatus + "\n"); sb.append("\nCertStatus: ").append(certStatus).append("\n");
if (certStatus == CertStatus.REVOKED) { if (certStatus == CertStatus.REVOKED) {
sb.append("revocationTime is " + revocationTime + "\n"); sb.append("revocationTime is ");
sb.append("revocationReason is " + revocationReason + "\n"); sb.append(revocationTime).append("\n");
sb.append("revocationReason is ");
sb.append(revocationReason).append("\n");
} }
sb.append("thisUpdate is " + thisUpdate + "\n"); sb.append("thisUpdate is ").append(thisUpdate).append("\n");
if (nextUpdate != null) { if (nextUpdate != null) {
sb.append("nextUpdate is " + nextUpdate + "\n"); sb.append("nextUpdate is ").append(nextUpdate).append("\n");
} }
for (java.security.cert.Extension ext : singleExtensions.values()) { for (java.security.cert.Extension ext : singleExtensions.values()) {
sb.append("singleExtension: " + ext + "\n"); sb.append("singleExtension: ");
sb.append(ext.toString()).append("\n");
} }
return sb.toString(); return sb.toString();
} }

View file

@ -0,0 +1,315 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider.certpath;
import java.util.Arrays;
import java.io.IOException;
import java.security.PublicKey;
import javax.security.auth.x500.X500Principal;
import sun.security.x509.KeyIdentifier;
import sun.security.util.DerValue;
/**
* Class for ResponderId entities as described in RFC6960. ResponderId objects
* are used to uniquely identify OCSP responders.
* <p>
* The RFC 6960 defines a ResponderID structure as:
* <pre>
* ResponderID ::= CHOICE {
* byName [1] Name,
* byKey [2] KeyHash }
*
* KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
* (excluding the tag and length fields)
*
* Name is defined in RFC 5280.
* </pre>
*
* @see ResponderId.Type
* @since 1.9
*/
public final class ResponderId {
/**
* A {@code ResponderId} enumeration describing the accepted forms for a
* {@code ResponderId}.
*
* @see ResponderId
* @since 1.9
*/
public static enum Type {
/**
* A BY_NAME {@code ResponderId} will be built from a subject name,
* either as an {@code X500Principal} or a DER-encoded byte array.
*/
BY_NAME(1, "byName"),
/**
* A BY_KEY {@code ResponderId} will be built from a public key
* identifier, either derived from a {@code PublicKey} or directly
* from a DER-encoded byte array containing the key identifier.
*/
BY_KEY(2, "byKey");
private final int tagNumber;
private final String ridTypeName;
private Type(int value, String name) {
this.tagNumber = value;
this.ridTypeName = name;
}
public int value() {
return tagNumber;
}
@Override
public String toString() {
return ridTypeName;
}
}
private Type type;
private X500Principal responderName;
private KeyIdentifier responderKeyId;
private byte[] encodedRid;
/**
* Constructs a {@code ResponderId} object using an {@code X500Principal}.
* When encoded in DER this object will use the BY_NAME option.
*
* @param subjectName the subject name of the certificate used
* to sign OCSP responses.
*
* @throws IOException if the internal DER-encoding of the
* {@code X500Principal} fails.
*/
public ResponderId(X500Principal subjectName) throws IOException {
responderName = subjectName;
responderKeyId = null;
encodedRid = principalToBytes();
type = Type.BY_NAME;
}
/**
* Constructs a {@code ResponderId} object using a {@code PublicKey}.
* When encoded in DER this object will use the byKey option, a
* SHA-1 hash of the responder's public key.
*
* @param pubKey the the OCSP responder's public key
*
* @throws IOException if the internal DER-encoding of the
* {@code KeyIdentifier} fails.
*/
public ResponderId(PublicKey pubKey) throws IOException {
responderKeyId = new KeyIdentifier(pubKey);
responderName = null;
encodedRid = keyIdToBytes();
type = Type.BY_KEY;
}
/**
* Constructs a {@code ResponderId} object from its DER-encoding.
*
* @param encodedData the DER-encoded bytes
*
* @throws IOException if the encodedData is not properly DER encoded
*/
public ResponderId(byte[] encodedData) throws IOException {
DerValue outer = new DerValue(encodedData);
if (outer.isContextSpecific((byte)Type.BY_NAME.value())
&& outer.isConstructed()) {
// Use the X500Principal constructor as a way to sanity
// check the incoming data.
responderName = new X500Principal(outer.getDataBytes());
encodedRid = principalToBytes();
type = Type.BY_NAME;
} else if (outer.isContextSpecific((byte)Type.BY_KEY.value())
&& outer.isConstructed()) {
// Use the KeyIdentifier constructor as a way to sanity
// check the incoming data.
responderKeyId =
new KeyIdentifier(new DerValue(outer.getDataBytes()));
encodedRid = keyIdToBytes();
type = Type.BY_KEY;
} else {
throw new IOException("Invalid ResponderId content");
}
}
/**
* Encode a {@code ResponderId} in DER form
*
* @return a byte array containing the DER-encoded representation for this
* {@code ResponderId}
*/
public byte[] getEncoded() {
return encodedRid.clone();
}
/**
* Return the type of {@ResponderId}
*
* @return a number corresponding to the context-specific tag number
* used in the DER-encoding for a {@code ResponderId}
*/
public ResponderId.Type getType() {
return type;
}
/**
* Get the length of the encoded {@code ResponderId} (including the tag and
* length of the explicit tagging from the outer ASN.1 CHOICE).
*
* @return the length of the encoded {@code ResponderId}
*/
public int length() {
return encodedRid.length;
}
/**
* Obtain the underlying {@code X500Principal} from a {@code ResponderId}
*
* @return the {@code X500Principal} for this {@code ResponderId} if it
* is a BY_NAME variant. If the {@code ResponderId} is a BY_KEY
* variant, this routine will return {@code null}.
*/
public X500Principal getResponderName() {
return responderName;
}
/**
* Obtain the underlying key identifier from a {@code ResponderId}
*
* @return the {@code KeyIdentifier} for this {@code ResponderId} if it
* is a BY_KEY variant. If the {@code ResponderId} is a BY_NAME
* variant, this routine will return {@code null}.
*/
public KeyIdentifier getKeyIdentifier() {
return responderKeyId;
}
/**
* Compares the specified object with this {@code ResponderId} for equality.
* A ResponderId will only be considered equivalent if both the type and
* data value are equal. Two ResponderIds initialized by name and
* key ID, respectively, will not be equal even if the
* ResponderId objects are created from the same source certificate.
*
* @param obj the object to be compared against
*
* @return true if the specified object is equal to this {@code Responderid}
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof ResponderId) {
ResponderId respObj = (ResponderId)obj;
return Arrays.equals(encodedRid, respObj.getEncoded());
}
return false;
}
/**
* Returns the hash code value for this {@code ResponderId}
*
* @return the hash code value for this {@code ResponderId}
*/
@Override
public int hashCode() {
return Arrays.hashCode(encodedRid);
}
/**
* Create a String representation of this {@code ResponderId}
*
* @return a String representation of this {@code ResponderId}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
switch (type) {
case BY_NAME:
sb.append(type).append(": ").append(responderName);
break;
case BY_KEY:
sb.append(type).append(": ");
for (byte keyIdByte : responderKeyId.getIdentifier()) {
sb.append(String.format("%02X", keyIdByte));
}
break;
default:
sb.append("Unknown ResponderId Type: ").append(type);
}
return sb.toString();
}
/**
* Convert the responderName data member into its DER-encoded form
*
* @return the DER encoding for a responder ID byName option, including
* explicit context-specific tagging.
*
* @throws IOException if any encoding error occurs
*/
private byte[] principalToBytes() throws IOException {
DerValue dv = new DerValue(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte)Type.BY_NAME.value()),
responderName.getEncoded());
return dv.toByteArray();
}
/**
* Convert the responderKeyId data member into its DER-encoded form
*
* @return the DER encoding for a responder ID byKey option, including
* explicit context-specific tagging.
*
* @throws IOException if any encoding error occurs
*/
private byte[] keyIdToBytes() throws IOException {
// Place the KeyIdentifier bytes into an OCTET STRING
DerValue inner = new DerValue(DerValue.tag_OctetString,
responderKeyId.getIdentifier());
// Mark the OCTET STRING-wrapped KeyIdentifier bytes
// as EXPLICIT CONTEXT 2
DerValue outer = new DerValue(DerValue.createTag(DerValue.TAG_CONTEXT,
true, (byte)Type.BY_KEY.value()), inner.toByteArray());
return outer.toByteArray();
}
}

View file

@ -0,0 +1,205 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
import java.util.Objects;
/*
* RFC6066 defines the TLS extension,"status_request" (type 0x5),
* which allows the client to request that the server perform OCSP
* on the client's behalf.
* The "extension data" field of this extension contains a
* "CertificateStatusRequest" structure:
*
* struct {
* CertificateStatusType status_type;
* select (status_type) {
* case ocsp: OCSPStatusRequest;
* } request;
* } CertificateStatusRequest;
*
* enum { ocsp(1), (255) } CertificateStatusType;
*
* struct {
* ResponderID responder_id_list<0..2^16-1>;
* Extensions request_extensions;
* } OCSPStatusRequest;
*
* opaque ResponderID<1..2^16-1>;
* opaque Extensions<0..2^16-1>;
*/
final class CertStatusReqExtension extends HelloExtension {
private final StatusRequestType statReqType;
private final StatusRequest request;
/**
* Construct the default status request extension object. The default
* object results in a status_request extension where the extension
* data segment is zero-length. This is used primarily in ServerHello
* messages where the server asserts it can do RFC 6066 status stapling.
*/
CertStatusReqExtension() {
super(ExtensionType.EXT_STATUS_REQUEST);
statReqType = null;
request = null;
}
/**
* Construct the status request extension object given a request type
* and {@code StatusRequest} object.
*
* @param reqType a {@code StatusRequestExtType object correspoding
* to the underlying {@code StatusRequest} object. A value of
* {@code null} is not allowed.
* @param statReq the {@code StatusRequest} object used to provide the
* encoding for the TLS extension. A value of {@code null} is not
* allowed.
*
* @throws IllegalArgumentException if the provided {@code StatusRequest}
* does not match the type.
* @throws NullPointerException if either the {@code reqType} or
* {@code statReq} arguments are {@code null}.
*/
CertStatusReqExtension(StatusRequestType reqType, StatusRequest statReq) {
super(ExtensionType.EXT_STATUS_REQUEST);
statReqType = Objects.requireNonNull(reqType,
"Unallowed null value for status_type");
request = Objects.requireNonNull(statReq,
"Unallowed null value for request");
// There is currently only one known status type (OCSP)
// We can add more clauses to cover other types in the future
if (statReqType == StatusRequestType.OCSP) {
if (!(statReq instanceof OCSPStatusRequest)) {
throw new IllegalArgumentException("StatusRequest not " +
"of type OCSPStatusRequest");
}
}
}
/**
* Construct the {@code CertStatusReqExtension} object from data read from
* a {@code HandshakeInputStream}
*
* @param s the {@code HandshakeInputStream} providing the encoded data
* @param len the length of the extension data
*
* @throws IOException if any decoding errors happen during object
* construction.
*/
CertStatusReqExtension(HandshakeInStream s, int len) throws IOException {
super(ExtensionType.EXT_STATUS_REQUEST);
if (len > 0) {
// Obtain the status type (first byte)
statReqType = StatusRequestType.get(s.getInt8());
if (statReqType == StatusRequestType.OCSP) {
request = new OCSPStatusRequest(s);
} else {
// This is a status_type we don't understand. Create
// an UnknownStatusRequest in order to preserve the data
request = new UnknownStatusRequest(s, len - 1);
}
} else {
// Treat this as a zero-length extension (i.e. from a ServerHello
statReqType = null;
request = null;
}
}
/**
* Return the length of the encoded extension, including extension type,
* extension length and status_type fields.
*
* @return the length in bytes, including the extension type and
* length fields.
*/
@Override
int length() {
return (statReqType != null ? 5 + request.length() : 4);
}
/**
* Send the encoded TLS extension through a {@code HandshakeOutputStream}
*
* @param s the {@code HandshakeOutputStream} used to send the encoded data
*
* @throws IOException tf any errors occur during the encoding process
*/
@Override
void send(HandshakeOutStream s) throws IOException {
s.putInt16(type.id);
s.putInt16(this.length() - 4);
if (statReqType != null) {
s.putInt8(statReqType.id);
request.send(s);
}
}
/**
* Create a string representation of this {@code CertStatusReqExtension}
*
* @return the string representation of this {@code CertStatusReqExtension}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Extension ").append(type);
if (statReqType != null) {
sb.append(": ").append(statReqType).append(", ").append(request);
}
return sb.toString();
}
/**
* Return the type field for this {@code CertStatusReqExtension}
*
* @return the {@code StatusRequestType} for this extension. {@code null}
* will be returned if the default constructor is used to create
* a zero length status_request extension (found in ServerHello
* messages)
*/
StatusRequestType getType() {
return statReqType;
}
/**
* Get the underlying {@code StatusRequest} for this
* {@code CertStatusReqExtension}
*
* @return the {@code StatusRequest} or {@code null} if the default
* constructor was used to create this extension.
*/
StatusRequest getRequest() {
return request;
}
}

View file

@ -0,0 +1,201 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
import javax.net.ssl.SSLException;
/*
* RFC6961 defines the TLS extension,"status_request_v2" (type 0x5),
* which allows the client to request that the server perform OCSP
* on the client's behalf.
*
* The RFC defines an CertStatusReqItemV2 structure:
*
* struct {
* CertificateStatusType status_type;
* uint16 request_length;
* select (status_type) {
* case ocsp: OCSPStatusRequest;
* case ocsp_multi: OCSPStatusRequest;
* } request;
* } CertificateStatusRequestItemV2;
*
* enum { ocsp(1), ocsp_multi(2), (255) } CertificateStatusType;
*/
final class CertStatusReqItemV2 implements StatusRequest {
private final StatusRequestType statReqType;
private final StatusRequest request;
/**
* Construct a {@code CertStatusReqItemV2} object using a type value
* and empty ResponderId and Extension lists.
*
* @param reqType the type of request (e.g. ocsp). A {@code null} value
* is not allowed.
* @param statReq the {@code StatusRequest} object used to provide the
* encoding for this {@code CertStatusReqItemV2}. A {@code null}
* value is not allowed.
*
* @throws IllegalArgumentException if the provided {@code StatusRequest}
* does not match the type.
* @throws NullPointerException if either the reqType or statReq arguments
* are {@code null}.
*/
CertStatusReqItemV2(StatusRequestType reqType, StatusRequest statReq) {
statReqType = Objects.requireNonNull(reqType,
"Unallowed null value for status_type");
request = Objects.requireNonNull(statReq,
"Unallowed null value for request");
// There is currently only one known status type (OCSP)
// We can add more clauses to cover other types in the future
if (statReqType.equals(StatusRequestType.OCSP) ||
statReqType.equals(StatusRequestType.OCSP_MULTI)) {
if (!(statReq instanceof OCSPStatusRequest)) {
throw new IllegalArgumentException("StatusRequest not " +
"of type OCSPStatusRequest");
}
}
}
/**
* Construct a {@code CertStatusReqItemV2} object from encoded bytes
*
* @param requestBytes the encoded bytes for the {@code CertStatusReqItemV2}
*
* @throws IOException if any decoding errors take place
* @throws IllegalArgumentException if the parsed reqType value is not a
* supported status request type.
*/
CertStatusReqItemV2(byte[] reqItemBytes) throws IOException {
ByteBuffer reqBuf = ByteBuffer.wrap(reqItemBytes);
statReqType = StatusRequestType.get(reqBuf.get());
int requestLength = Short.toUnsignedInt(reqBuf.getShort());
if (requestLength == reqBuf.remaining()) {
byte[] statReqBytes = new byte[requestLength];
reqBuf.get(statReqBytes);
if (statReqType == StatusRequestType.OCSP ||
statReqType == StatusRequestType.OCSP_MULTI) {
request = new OCSPStatusRequest(statReqBytes);
} else {
request = new UnknownStatusRequest(statReqBytes);
}
} else {
throw new SSLException("Incorrect request_length: " +
"Expected " + reqBuf.remaining() + ", got " +
requestLength);
}
}
/**
* Construct an {@code CertStatusReqItemV2} object from data read from
* a {@code HandshakeInputStream}
*
* @param s the {@code HandshakeInputStream} providing the encoded data
*
* @throws IOException if any decoding errors happen during object
* construction.
* @throws IllegalArgumentException if the parsed reqType value is not a
* supported status request type.
*/
CertStatusReqItemV2(HandshakeInStream in) throws IOException {
statReqType = StatusRequestType.get(in.getInt8());
int requestLength = in.getInt16();
if (statReqType == StatusRequestType.OCSP ||
statReqType == StatusRequestType.OCSP_MULTI) {
request = new OCSPStatusRequest(in);
} else {
request = new UnknownStatusRequest(in, requestLength);
}
}
/**
* Return the length of this {@code CertStatusReqItemV2} in its encoded form
*
* @return the encoded length of this {@code CertStatusReqItemV2}
*/
@Override
public int length() {
// The length is the the status type (1 byte) + the request length
// field (2 bytes) + the StatusRequest data length.
return request.length() + 3;
}
/**
* Send the encoded {@code CertStatusReqItemV2} through a
* {@code HandshakeOutputStream}
*
* @param s the {@code HandshakeOutputStream} used to send the encoded data
*
* @throws IOException if any errors occur during the encoding process
*/
@Override
public void send(HandshakeOutStream s) throws IOException {
s.putInt8(statReqType.id);
s.putInt16(request.length());
request.send(s);
}
/**
* Create a string representation of this {@code CertStatusReqItemV2}
*
* @return the string representation of this {@code CertStatusReqItemV2}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("CertStatusReqItemV2: ").append(statReqType).append(", ");
sb.append(request.toString());
return sb.toString();
}
/**
* Return the type field for this {@code CertStatusReqItemV2}
*
* @return the {@code StatusRequestType} for this extension.
*/
StatusRequestType getType() {
return statReqType;
}
/**
* Get the underlying {@code StatusRequest} for this
* {@code CertStatusReqItemV2}
*
* @return the {@code StatusRequest}
*/
StatusRequest getRequest() {
return request;
}
}

View file

@ -0,0 +1,220 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
import java.util.List;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Objects;
import javax.net.ssl.SSLException;
/*
* RFC6066 defines the TLS extension,"status_request" (type 0x5),
* which allows the client to request that the server perform OCSP
* on the client's behalf.
* The "extension data" field of this extension contains a
* "CertificateStatusRequest" structure:
*
* struct {
* CertificateStatusType status_type;
* select (status_type) {
* case ocsp: OCSPStatusRequest;
* } request;
* } CertificateStatusRequest;
*
* enum { ocsp(1), (255) } CertificateStatusType;
*
* struct {
* ResponderID responder_id_list<0..2^16-1>;
* Extensions request_extensions;
* } OCSPStatusRequest;
*
* opaque ResponderID<1..2^16-1>;
* opaque Extensions<0..2^16-1>;
*/
final class CertStatusReqListV2Extension extends HelloExtension {
private final List<CertStatusReqItemV2> itemList;
private final int itemListLength;
/**
* Construct a default {@code CertStatusReqListV2Extension}. The default
* object results in a status_request_v2 extension where the extension
* data segment is zero-length. This is used primarily in ServerHello
* messages where the server asserts it can do RFC 6961 status stapling.
*/
CertStatusReqListV2Extension() {
super(ExtensionType.EXT_STATUS_REQUEST_V2);
itemList = Collections.emptyList();
itemListLength = 0;
}
/**
* Construct a {@code CertStatusReqListV2Extension} from a provided list
* of {@code CertStatusReqItemV2} objects.
*
* @param reqList a {@code List} containing one or more
* {@code CertStatusReqItemV2} objects to be included in this TLS
* Hello extension. Passing an empty list will result in the encoded
* extension having a zero-length extension_data segment, and is
* the same as using the default constructor.
*
* @throws NullPointerException if reqList is {@code null}
*/
CertStatusReqListV2Extension(List<CertStatusReqItemV2> reqList) {
super(ExtensionType.EXT_STATUS_REQUEST_V2);
Objects.requireNonNull(reqList,
"Unallowed null value for certificate_status_req_list");
itemList = Collections.unmodifiableList(new ArrayList<>(reqList));
itemListLength = calculateListLength();
}
/**
* Construct the {@code CertStatusReqListV2Extension} object from data
* read from a {@code HandshakeInputStream}
*
* @param s the {@code HandshakeInputStream} providing the encoded data
* @param len the length of the extension data
*
* @throws IOException if any decoding errors happen during object
* construction.
*/
CertStatusReqListV2Extension(HandshakeInStream s, int len)
throws IOException {
super(ExtensionType.EXT_STATUS_REQUEST_V2);
if (len <= 0) {
// Handle the empty extension data case (from a ServerHello)
itemList = Collections.emptyList();
itemListLength = 0;
} else {
List<CertStatusReqItemV2> workingList = new ArrayList<>();
itemListLength = s.getInt16();
if (itemListLength <= 0) {
throw new SSLException("certificate_status_req_list length " +
"must be greater than zero (received length: " +
itemListLength + ")");
}
int totalRead = 0;
CertStatusReqItemV2 reqItem;
do {
reqItem = new CertStatusReqItemV2(s);
totalRead += reqItem.length();
} while (workingList.add(reqItem) && totalRead < itemListLength);
// If for some reason the add returns false, we may not have read
// all the necessary bytes from the stream. Check this and throw
// an exception if we terminated the loop early.
if (totalRead != itemListLength) {
throw new SSLException("Not all certificate_status_req_list " +
"bytes were read: expected " + itemListLength +
", read " + totalRead);
}
itemList = Collections.unmodifiableList(workingList);
}
}
/**
* Get the list of {@code CertStatusReqItemV2} objects for this extension
*
* @return an unmodifiable list of {@code CertStatusReqItemV2} objects
*/
List<CertStatusReqItemV2> getRequestItems() {
return itemList;
}
/**
* Return the length of the encoded extension, including extension type
* and extension length fields.
*
* @return the length in bytes, including the extension type and
* extension_data length.
*/
@Override
int length() {
return (itemList.isEmpty() ? 4 : itemListLength + 6);
}
/**
* Send the encoded {@code CertStatusReqListV2Extension} through a
* {@code HandshakeOutputStream}
*
* @param s the {@code HandshakeOutputStream} used to send the encoded data
*
* @throws IOException if any errors occur during the encoding process
*/
@Override
void send(HandshakeOutStream s) throws IOException {
s.putInt16(type.id);
s.putInt16(this.length() - 4);
if (itemListLength > 0) {
s.putInt16(itemListLength);
for (CertStatusReqItemV2 item : itemList) {
item.send(s);
}
}
}
/**
* Create a string representation of this
* {@code CertStatusReqListV2Extension}
*
* @return the string representation of this
* {@code CertStatusReqListV2Extension}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Extension ").append(type);
for (CertStatusReqItemV2 item : itemList) {
sb.append("\n").append(item);
}
return sb.toString();
}
/**
* Determine the length of the certificate_status_req_list field in
* the status_request_v2 extension.
*
* @return the total encoded length of all items in the list, or 0 if the
* encapsulating extension_data is zero-length (from a ServerHello)
*/
private int calculateListLength() {
int listLen = 0;
for (CertStatusReqItemV2 item : itemList) {
listLen += item.length();
}
return listLen;
}
}

View file

@ -37,10 +37,12 @@ import java.security.spec.ECParameterSpec;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.Reason;
import java.security.cert.CertPathValidatorException.BasicReason;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.*; import javax.net.ssl.*;
@ -79,6 +81,12 @@ final class ClientHandshaker extends Handshaker {
private boolean serverKeyExchangeReceived; private boolean serverKeyExchangeReceived;
private final boolean enableStatusRequestExtension =
Debug.getBooleanProperty(
"jdk.tls.client.enableStatusRequestExtension", true);
private boolean staplingActive = false;
private X509Certificate[] deferredCerts;
/* /*
* The RSA PreMasterSecret needs to know the version of * The RSA PreMasterSecret needs to know the version of
* ClientHello that was used on this handshake. This represents * ClientHello that was used on this handshake. This represents
@ -200,7 +208,16 @@ final class ClientHandshaker extends Handshaker {
@Override @Override
void processMessage(byte type, int messageLen) throws IOException { void processMessage(byte type, int messageLen) throws IOException {
// check the handshake state // check the handshake state
handshakeState.check(type); List<Byte> ignoredOptStates = handshakeState.check(type);
// If the state machine has skipped over certificate status
// and stapling was enabled, we need to check the chain immediately
// because it was deferred, waiting for CertificateStatus.
if (staplingActive && ignoredOptStates.contains(
HandshakeMessage.ht_certificate_status)) {
checkServerCerts(deferredCerts);
serverKey = session.getPeerCertificates()[0].getPublicKey();
}
switch (type) { switch (type) {
case HandshakeMessage.ht_hello_request: case HandshakeMessage.ht_hello_request:
@ -241,8 +258,19 @@ final class ClientHandshaker extends Handshaker {
CertificateMsg certificateMsg = new CertificateMsg(input); CertificateMsg certificateMsg = new CertificateMsg(input);
handshakeState.update(certificateMsg, resumingSession); handshakeState.update(certificateMsg, resumingSession);
this.serverCertificate(certificateMsg); this.serverCertificate(certificateMsg);
serverKey = if (!staplingActive) {
session.getPeerCertificates()[0].getPublicKey(); // If we are not doing stapling, we can set serverKey right
// away. Otherwise, we will wait until verification of the
// chain has completed after CertificateStatus;
serverKey = session.getPeerCertificates()[0].getPublicKey();
}
break;
case HandshakeMessage.ht_certificate_status:
CertificateStatus certStatusMsg = new CertificateStatus(input);
handshakeState.update(certStatusMsg, resumingSession);
this.certificateStatus(certStatusMsg);
serverKey = session.getPeerCertificates()[0].getPublicKey();
break; break;
case HandshakeMessage.ht_server_key_exchange: case HandshakeMessage.ht_server_key_exchange:
@ -685,10 +713,22 @@ final class ClientHandshaker extends Handshaker {
ExtensionType type = ext.type; ExtensionType type = ext.type;
if (type == ExtensionType.EXT_SERVER_NAME) { if (type == ExtensionType.EXT_SERVER_NAME) {
serverNamesAccepted = true; serverNamesAccepted = true;
} else if (type == ExtensionType.EXT_STATUS_REQUEST ||
type == ExtensionType.EXT_STATUS_REQUEST_V2) {
// Only enable the stapling feature if the client asserted
// these extensions.
if (enableStatusRequestExtension) {
staplingActive = true;
} else {
fatalSE(Alerts.alert_unexpected_message, "Server set " +
type + " extension when not requested by client");
}
} else if ((type != ExtensionType.EXT_ELLIPTIC_CURVES) } else if ((type != ExtensionType.EXT_ELLIPTIC_CURVES)
&& (type != ExtensionType.EXT_EC_POINT_FORMATS) && (type != ExtensionType.EXT_EC_POINT_FORMATS)
&& (type != ExtensionType.EXT_SERVER_NAME) && (type != ExtensionType.EXT_SERVER_NAME)
&& (type != ExtensionType.EXT_RENEGOTIATION_INFO)) { && (type != ExtensionType.EXT_RENEGOTIATION_INFO)
&& (type != ExtensionType.EXT_STATUS_REQUEST)
&& (type != ExtensionType.EXT_STATUS_REQUEST_V2)) {
fatalSE(Alerts.alert_unsupported_extension, fatalSE(Alerts.alert_unsupported_extension,
"Server sent an unsupported extension: " + type); "Server sent an unsupported extension: " + type);
} }
@ -1476,6 +1516,12 @@ final class ClientHandshaker extends Handshaker {
} }
} }
// Add status_request and status_request_v2 extensions
if (enableStatusRequestExtension) {
clientHelloMessage.addCertStatusReqListV2Extension();
clientHelloMessage.addCertStatusRequestExtension();
}
// reset the client random cookie // reset the client random cookie
clnt_random = clientHelloMessage.clnt_random; clnt_random = clientHelloMessage.clnt_random;
@ -1545,40 +1591,36 @@ final class ClientHandshaker extends Handshaker {
} }
// ask the trust manager to verify the chain // ask the trust manager to verify the chain
X509TrustManager tm = sslContext.getX509TrustManager(); if (staplingActive) {
try { // Defer the certificate check until after we've received the
// find out the key exchange algorithm used // CertificateStatus message. If that message doesn't come in
// use "RSA" for non-ephemeral "RSA_EXPORT" // immediately following this message we will execute the check
String keyExchangeString; // directly from processMessage before any other SSL/TLS processing.
if (keyExchange == K_RSA_EXPORT && !serverKeyExchangeReceived) { deferredCerts = peerCerts;
keyExchangeString = K_RSA.name;
} else { } else {
keyExchangeString = keyExchange.name; // We're not doing stapling, so perform the check right now
checkServerCerts(peerCerts);
}
} }
if (tm instanceof X509ExtendedTrustManager) { /**
if (conn != null) { * If certificate status stapling has been enabled, the server will send
((X509ExtendedTrustManager)tm).checkServerTrusted( * one or more status messages to the client.
peerCerts.clone(), *
keyExchangeString, * @param mesg a {@code CertificateStatus} object built from the data
conn); * sent by the server.
} else { *
((X509ExtendedTrustManager)tm).checkServerTrusted( * @throws IOException if any parsing errors occur.
peerCerts.clone(), */
keyExchangeString, private void certificateStatus(CertificateStatus mesg) throws IOException {
engine); if (debug != null && Debug.isOn("handshake")) {
mesg.print(System.out);
} }
} else {
// Unlikely to happen, because we have wrapped the old // Perform the certificate check using the deferred certificates
// X509TrustManager with the new X509ExtendedTrustManager. // and responses that we have obtained.
throw new CertificateException( session.setStatusResponses(mesg.getResponses());
"Improper X509TrustManager implementation"); checkServerCerts(deferredCerts);
}
} catch (CertificateException e) {
// This will throw an exception, so include the original error.
fatalSE(Alerts.alert_certificate_unknown, e);
}
session.setPeerCertificates(peerCerts);
} }
/* /*
@ -1700,4 +1742,88 @@ final class ClientHandshaker extends Handshaker {
return false; return false;
} }
/**
* Perform client-side checking of server certificates.
*
* @param certs an array of {@code X509Certificate} objects presented
* by the server in the ServerCertificate message.
*
* @throws IOException if a failure occurs during validation or
* the trust manager associated with the {@code SSLContext} is not
* an {@code X509ExtendedTrustManager}.
*/
private void checkServerCerts(X509Certificate[] certs)
throws IOException {
X509TrustManager tm = sslContext.getX509TrustManager();
// find out the key exchange algorithm used
// use "RSA" for non-ephemeral "RSA_EXPORT"
String keyExchangeString;
if (keyExchange == K_RSA_EXPORT && !serverKeyExchangeReceived) {
keyExchangeString = K_RSA.name;
} else {
keyExchangeString = keyExchange.name;
} }
try {
if (tm instanceof X509ExtendedTrustManager) {
if (conn != null) {
((X509ExtendedTrustManager)tm).checkServerTrusted(
certs.clone(),
keyExchangeString,
conn);
} else {
((X509ExtendedTrustManager)tm).checkServerTrusted(
certs.clone(),
keyExchangeString,
engine);
}
} else {
// Unlikely to happen, because we have wrapped the old
// X509TrustManager with the new X509ExtendedTrustManager.
throw new CertificateException(
"Improper X509TrustManager implementation");
}
// Once the server certificate chain has been validated, set
// the certificate chain in the TLS session.
session.setPeerCertificates(certs);
} catch (CertificateException ce) {
fatalSE(getCertificateAlert(ce), ce);
}
}
/**
* When a failure happens during certificate checking from an
* {@link X509TrustManager}, determine what TLS alert description to use.
*
* @param cexc The exception thrown by the {@link X509TrustManager}
*
* @return A byte value corresponding to a TLS alert description number.
*/
private byte getCertificateAlert(CertificateException cexc) {
// The specific reason for the failure will determine how to
// set the alert description value
byte alertDesc = Alerts.alert_certificate_unknown;
Throwable baseCause = cexc.getCause();
if (baseCause instanceof CertPathValidatorException) {
CertPathValidatorException cpve =
(CertPathValidatorException)baseCause;
Reason reason = cpve.getReason();
if (reason == BasicReason.REVOKED) {
alertDesc = staplingActive ?
Alerts.alert_bad_certificate_status_response :
Alerts.alert_certificate_revoked;
} else if (reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) {
alertDesc = staplingActive ?
Alerts.alert_bad_certificate_status_response :
Alerts.alert_certificate_unknown;
}
}
return alertDesc;
}
}

View file

@ -44,7 +44,7 @@ final class ExtensionType {
} }
static List<ExtensionType> knownExtensions = static List<ExtensionType> knownExtensions =
new ArrayList<ExtensionType>(13); new ArrayList<ExtensionType>(14);
static ExtensionType get(int id) { static ExtensionType get(int id) {
for (ExtensionType ext : knownExtensions) { for (ExtensionType ext : knownExtensions) {
@ -97,6 +97,10 @@ final class ExtensionType {
final static ExtensionType EXT_SIGNATURE_ALGORITHMS = final static ExtensionType EXT_SIGNATURE_ALGORITHMS =
e(0x000D, "signature_algorithms"); // IANA registry value: 13 e(0x000D, "signature_algorithms"); // IANA registry value: 13
// extensions defined in RFC 6961
final static ExtensionType EXT_STATUS_REQUEST_V2 =
e(0x0011, "status_request_v2"); // IANA registry value: 17
// extensions defined in RFC 5746 // extensions defined in RFC 5746
final static ExtensionType EXT_RENEGOTIATION_INFO = final static ExtensionType EXT_RENEGOTIATION_INFO =
e(0xff01, "renegotiation_info"); // IANA registry value: 65281 e(0xff01, "renegotiation_info"); // IANA registry value: 65281

View file

@ -49,6 +49,7 @@ import sun.security.internal.spec.TlsPrfParameterSpec;
import sun.security.ssl.CipherSuite.*; import sun.security.ssl.CipherSuite.*;
import static sun.security.ssl.CipherSuite.PRF.*; import static sun.security.ssl.CipherSuite.PRF.*;
import sun.security.util.KeyUtil; import sun.security.util.KeyUtil;
import sun.security.provider.certpath.OCSPResponse;
/** /**
* Many data structures are involved in the handshake messages. These * Many data structures are involved in the handshake messages. These
@ -393,6 +394,24 @@ static final class ClientHello extends HandshakeMessage {
cookieDigest.update(hos.toByteArray()); cookieDigest.update(hos.toByteArray());
} }
// Add status_request extension type
void addCertStatusRequestExtension() {
extensions.add(new CertStatusReqExtension(StatusRequestType.OCSP,
new OCSPStatusRequest()));
}
// Add status_request_v2 extension type
void addCertStatusReqListV2Extension() {
// Create a default OCSPStatusRequest that we can use for both
// OCSP_MULTI and OCSP request list items.
OCSPStatusRequest osr = new OCSPStatusRequest();
List<CertStatusReqItemV2> itemList = new ArrayList<>(2);
itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI,
osr));
itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, osr));
extensions.add(new CertStatusReqListV2Extension(itemList));
}
@Override @Override
int messageType() { return ht_client_hello; } int messageType() { return ht_client_hello; }
@ -635,6 +654,240 @@ class CertificateMsg extends HandshakeMessage
} }
} }
/*
* CertificateStatus ... SERVER --> CLIENT
*
* When a ClientHello asserting the status_request or status_request_v2
* extensions is accepted by the server, it will fetch and return one
* or more status responses in this handshake message.
*
* NOTE: Like the Certificate handshake message, this can potentially
* be a very large message both due to the size of multiple status
* responses and the certificate chains that are often attached to them.
* Up to 2^24 bytes of status responses may be sent, possibly fragmented
* over multiple TLS records.
*/
static final class CertificateStatus extends HandshakeMessage
{
private final StatusRequestType statusType;
private int encodedResponsesLen;
private int messageLength = -1;
private List<byte[]> encodedResponses;
@Override
int messageType() { return ht_certificate_status; }
/**
* Create a CertificateStatus message from the certificates and their
* respective OCSP responses
*
* @param type an indication of the type of response (OCSP or OCSP_MULTI)
* @param responses a {@code List} of OCSP responses in DER-encoded form.
* For the OCSP type, only the first entry in the response list is
* used, and must correspond to the end-entity certificate sent to the
* peer. Zero-length or null values for the response data are not
* allowed for the OCSP type. For the OCSP_MULTI type, each entry in
* the list should match its corresponding certificate sent in the
* Server Certificate message. Where an OCSP response does not exist,
* either a zero-length array or a null value should be used.
*
* @throws SSLException if an unsupported StatusRequestType or invalid
* OCSP response data is provided.
*/
CertificateStatus(StatusRequestType type, X509Certificate[] chain,
Map<X509Certificate, byte[]> responses) throws SSLException {
statusType = type;
encodedResponsesLen = 0;
encodedResponses = new ArrayList<>(chain.length);
Objects.requireNonNull(chain, "Null chain not allowed");
Objects.requireNonNull(responses, "Null responses not allowed");
if (statusType == StatusRequestType.OCSP) {
// Just get the response for the end-entity certificate
byte[] respDER = responses.get(chain[0]);
if (respDER != null && respDER.length > 0) {
encodedResponses.add(respDER);
encodedResponsesLen = 3 + respDER.length;
} else {
throw new SSLHandshakeException("Zero-length or null " +
"OCSP Response");
}
} else if (statusType == StatusRequestType.OCSP_MULTI) {
for (X509Certificate cert : chain) {
byte[] respDER = responses.get(cert);
if (respDER != null) {
encodedResponses.add(respDER);
encodedResponsesLen += (respDER.length + 3);
} else {
// If we cannot find a response for a given certificate
// then use a zero-length placeholder.
encodedResponses.add(new byte[0]);
encodedResponsesLen += 3;
}
}
} else {
throw new SSLHandshakeException("Unsupported StatusResponseType: " +
statusType);
}
}
/**
* Decode the CertificateStatus handshake message coming from a
* {@code HandshakeInputStream}.
*
* @param input the {@code HandshakeInputStream} containing the
* CertificateStatus message bytes.
*
* @throws SSLHandshakeException if a zero-length response is found in the
* OCSP response type, or an unsupported response type is detected.
* @throws IOException if a decoding error occurs.
*/
CertificateStatus(HandshakeInStream input) throws IOException {
encodedResponsesLen = 0;
encodedResponses = new ArrayList<>();
statusType = StatusRequestType.get(input.getInt8());
if (statusType == StatusRequestType.OCSP) {
byte[] respDER = input.getBytes24();
// Convert the incoming bytes to a OCSPResponse strucutre
if (respDER.length > 0) {
encodedResponses.add(respDER);
encodedResponsesLen = 3 + respDER.length;
} else {
throw new SSLHandshakeException("Zero-length OCSP Response");
}
} else if (statusType == StatusRequestType.OCSP_MULTI) {
int respListLen = input.getInt24();
encodedResponsesLen = respListLen;
// Add each OCSP reponse into the array list in the order
// we receive them off the wire. A zero-length array is
// allowed for ocsp_multi, and means that a response for
// a given certificate is not available.
while (respListLen > 0) {
byte[] respDER = input.getBytes24();
encodedResponses.add(respDER);
respListLen -= (respDER.length + 3);
}
if (respListLen != 0) {
throw new SSLHandshakeException(
"Bad OCSP response list length");
}
} else {
throw new SSLHandshakeException("Unsupported StatusResponseType: " +
statusType);
}
}
/**
* Get the length of the CertificateStatus message.
*
* @return the length of the message in bytes.
*/
@Override
int messageLength() {
int len = 1; // Length + Status type
if (messageLength == -1) {
if (statusType == StatusRequestType.OCSP) {
len += encodedResponsesLen;
} else if (statusType == StatusRequestType.OCSP_MULTI) {
len += 3 + encodedResponsesLen;
}
messageLength = len;
}
return messageLength;
}
/**
* Encode the CertificateStatus handshake message and place it on a
* {@code HandshakeOutputStream}.
*
* @param s the HandshakeOutputStream that will the message bytes.
*
* @throws IOException if an encoding error occurs.
*/
@Override
void send(HandshakeOutStream s) throws IOException {
s.putInt8(statusType.id);
if (statusType == StatusRequestType.OCSP) {
s.putBytes24(encodedResponses.get(0));
} else if (statusType == StatusRequestType.OCSP_MULTI) {
s.putInt24(encodedResponsesLen);
for (byte[] respBytes : encodedResponses) {
if (respBytes != null) {
s.putBytes24(respBytes);
} else {
s.putBytes24(null);
}
}
} else {
// It is highly unlikely that we will fall into this section of
// the code.
throw new SSLHandshakeException("Unsupported status_type: " +
statusType.id);
}
}
/**
* Display a human-readable representation of the CertificateStatus message.
*
* @param s the PrintStream used to display the message data.
*
* @throws IOException if any errors occur while parsing the OCSP response
* bytes into a readable form.
*/
@Override
void print(PrintStream s) throws IOException {
s.println("*** CertificateStatus");
if (debug != null && Debug.isOn("verbose")) {
s.println("Type: " + statusType);
if (statusType == StatusRequestType.OCSP) {
OCSPResponse oResp = new OCSPResponse(encodedResponses.get(0));
s.println(oResp);
} else if (statusType == StatusRequestType.OCSP_MULTI) {
int numResponses = encodedResponses.size();
s.println(numResponses +
(numResponses == 1 ? " entry:" : " entries:"));
for (byte[] respDER : encodedResponses) {
if (respDER.length > 0) {
OCSPResponse oResp = new OCSPResponse(respDER);
s.println(oResp);
} else {
s.println("<Zero-length entry>");
}
}
}
}
}
/**
* Get the type of CertificateStatus message
*
* @return the {@code StatusRequestType} for this CertificateStatus
* message.
*/
StatusRequestType getType() {
return statusType;
}
/**
* Get the list of non-zero length OCSP responses.
* The responses returned in this list can be used to map to
* {@code X509Certificate} objects provided by the peer and
* provided to a {@code PKIXRevocationChecker}.
*
* @return an unmodifiable List of zero or more byte arrays, each one
* consisting of a single status response.
*/
List<byte[]> getResponses() {
return Collections.unmodifiableList(encodedResponses);
}
}
/* /*
* ServerKeyExchange ... SERVER --> CLIENT * ServerKeyExchange ... SERVER --> CLIENT
* *

View file

@ -25,12 +25,12 @@
package sun.security.ssl; package sun.security.ssl;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.HashMap; import java.util.HashMap;
import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLProtocolException;
import sun.security.ssl.HandshakeMessage.*;
import static sun.security.ssl.CipherSuite.KeyExchange; import static sun.security.ssl.CipherSuite.KeyExchange;
import static sun.security.ssl.CipherSuite.KeyExchange.*; import static sun.security.ssl.CipherSuite.KeyExchange.*;
import static sun.security.ssl.HandshakeStateManager.HandshakeState.*; import static sun.security.ssl.HandshakeStateManager.HandshakeState.*;
@ -311,7 +311,7 @@ final class HandshakeStateManager {
HS_SERVER_CHANGE_CIPHER_SPEC( HS_SERVER_CHANGE_CIPHER_SPEC(
"server change_cipher_spec", "server change_cipher_spec",
HandshakeMessage.ht_not_applicable), HandshakeMessage.ht_not_applicable),
HS_SERVER_FINISHDE( HS_SERVER_FINISHED(
"server finished", "server finished",
HandshakeMessage.ht_finished); HandshakeMessage.ht_finished);
@ -343,7 +343,8 @@ final class HandshakeStateManager {
return upcomingStates.isEmpty(); return upcomingStates.isEmpty();
} }
void check(byte handshakeType) throws SSLProtocolException { List<Byte> check(byte handshakeType) throws SSLProtocolException {
List<Byte> ignoredOptional = new LinkedList<>();
String exceptionMsg = String exceptionMsg =
"Handshake message sequence violation, " + handshakeType; "Handshake message sequence violation, " + handshakeType;
@ -362,27 +363,28 @@ final class HandshakeStateManager {
} }
// It is a kickstart message. // It is a kickstart message.
return; return Collections.emptyList();
} }
// Ignore the checking for HelloRequest messages as they are // Ignore the checking for HelloRequest messages as they
// may be sent by the server at any time. // may be sent by the server at any time.
if (handshakeType == HandshakeMessage.ht_hello_request) { if (handshakeType == HandshakeMessage.ht_hello_request) {
return; return Collections.emptyList();
} }
for (HandshakeState handshakeState : upcomingStates) { for (HandshakeState handshakeState : upcomingStates) {
if (handshakeState.handshakeType == handshakeType) { if (handshakeState.handshakeType == handshakeType) {
// It's the expected next handshake type. // It's the expected next handshake type.
return; return ignoredOptional;
} }
if (handshakeState.isOptional) { if (handshakeState.isOptional) {
ignoredOptional.add(handshakeState.handshakeType);
continue; continue;
} else { } else {
for (HandshakeState alternative : alternatives) { for (HandshakeState alternative : alternatives) {
if (alternative.handshakeType == handshakeType) { if (alternative.handshakeType == handshakeType) {
return; return ignoredOptional;
} }
if (alternative.isOptional) { if (alternative.isOptional) {
@ -541,7 +543,7 @@ final class HandshakeStateManager {
// (Server Finished Flight) // (Server Finished Flight)
// HS_NEW_SESSION_TICKET // HS_NEW_SESSION_TICKET
// --> HS_SERVER_CHANGE_CIPHER_SPEC // --> HS_SERVER_CHANGE_CIPHER_SPEC
// --> HS_SERVER_FINISHDE // --> HS_SERVER_FINISHED
// (Client Finished Flight) // (Client Finished Flight)
// --> HS_CLIENT_CHANGE_CIPHER_SPEC // --> HS_CLIENT_CHANGE_CIPHER_SPEC
// --> HS_CLEINT_FINISHED // --> HS_CLEINT_FINISHED
@ -587,7 +589,7 @@ final class HandshakeStateManager {
// Mandatory server ChangeCipherSpec and Finished messages // Mandatory server ChangeCipherSpec and Finished messages
upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC); upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
upcomingStates.add(HS_SERVER_FINISHDE); upcomingStates.add(HS_SERVER_FINISHED);
// Mandatory client ChangeCipherSpec and Finished messages // Mandatory client ChangeCipherSpec and Finished messages
upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC); upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
@ -598,15 +600,11 @@ final class HandshakeStateManager {
// boolean hasSupplementalDataExt = // boolean hasSupplementalDataExt =
// (hes.get(HandshakeMessage.ht_supplemental_data) != null); // (hes.get(HandshakeMessage.ht_supplemental_data) != null);
// Not support CertificateStatus extension yet.
//
// boolean hasCertificateStatusExt =
// (hes.get(HandshakeMessage.ht_certificate_status) != null);
// Not support CertificateURL extension yet. // Not support CertificateURL extension yet.
// //
// boolean hasCertificateUrlExt = // boolean hasCertificateUrlExt =
// (hes.get(HandshakeMessage.ht_certificate_url) != null); // (hes.get(ExtensionType EXT_CLIENT_CERTIFICATE_URL)
// != null);
// Not support SupplementalData extension yet. // Not support SupplementalData extension yet.
// //
@ -625,12 +623,11 @@ final class HandshakeStateManager {
upcomingStates.add(HS_SERVER_CERTIFICATE); upcomingStates.add(HS_SERVER_CERTIFICATE);
} }
// Not support CertificateStatus extension yet. // Optional CertificateStatus message
// if (hes.get(ExtensionType.EXT_STATUS_REQUEST) != null ||
// // Optional CertificateStatus message hes.get(ExtensionType.EXT_STATUS_REQUEST_V2) != null) {
// if (hasCertificateStatusExt) { upcomingStates.add(HS_CERTIFICATE_STATUS);
// upcomingStates.add(HS_CERTIFICATE_STATUS); }
// }
// Need ServerKeyExchange message or not? // Need ServerKeyExchange message or not?
if ((keyExchange == K_RSA_EXPORT) || if ((keyExchange == K_RSA_EXPORT) ||
@ -690,7 +687,7 @@ final class HandshakeStateManager {
// Mandatory server ChangeCipherSpec and Finished messages // Mandatory server ChangeCipherSpec and Finished messages
upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC); upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
upcomingStates.add(HS_SERVER_FINISHDE); upcomingStates.add(HS_SERVER_FINISHED);
} }
break; break;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2006, 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2006, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -87,6 +87,10 @@ final class HelloExtensions {
extension = new RenegotiationInfoExtension(s, extlen); extension = new RenegotiationInfoExtension(s, extlen);
} else if (extType == ExtensionType.EXT_MAX_FRAGMENT_LENGTH) { } else if (extType == ExtensionType.EXT_MAX_FRAGMENT_LENGTH) {
extension = new MaxFragmentLengthExtension(s, extlen); extension = new MaxFragmentLengthExtension(s, extlen);
} else if (extType == ExtensionType.EXT_STATUS_REQUEST) {
extension = new CertStatusReqExtension(s, extlen);
} else if (extType == ExtensionType.EXT_STATUS_REQUEST_V2) {
extension = new CertStatusReqListV2Extension(s, extlen);
} else { } else {
extension = new UnknownExtension(s, extlen, extType); extension = new UnknownExtension(s, extlen, extType);
} }

View file

@ -0,0 +1,358 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.cert.Extension;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import javax.net.ssl.SSLException;
import sun.security.util.DerValue;
import sun.security.util.DerInputStream;
import sun.security.util.DerOutputStream;
import sun.security.provider.certpath.ResponderId;
/*
* RFC6066 defines the TLS extension,"status_request" (type 0x5),
* which allows the client to request that the server perform OCSP
* on the client's behalf.
*
* The RFC defines an OCSPStatusRequest structure:
*
* struct {
* ResponderID responder_id_list<0..2^16-1>;
* Extensions request_extensions;
* } OCSPStatusRequest;
*/
final class OCSPStatusRequest implements StatusRequest {
private final List<ResponderId> responderIds;
private final List<Extension> extensions;
private int encodedLen;
private int ridListLen;
private int extListLen;
/**
* Construct a default {@code OCSPStatusRequest} object with empty
* responder ID and code extension list fields.
*/
OCSPStatusRequest() {
responderIds = new ArrayList<>();
extensions = new ArrayList<>();
encodedLen = this.length();
}
/**
* Construct an {@code OCSPStatusRequest} object using the provided
* {@code ResponderId} and {@code Extension} lists.
*
* @param respIds the list of {@code ResponderId} objects to be placed
* into the {@code OCSPStatusRequest}. If the user wishes to place
* no {@code ResponderId} objects in the request, either an empty
* {@code List} or {@code null} is acceptable.
* @param exts the list of {@code Extension} objects to be placed into
* the {@code OCSPStatusRequest} If the user wishes to place
* no {@code Extension} objects in the request, either an empty
* {@code List} or {@code null} is acceptable.
*/
OCSPStatusRequest(List<ResponderId> respIds, List<Extension> exts) {
responderIds = new ArrayList<>(respIds != null ? respIds :
Collections.emptyList());
extensions = new ArrayList<>(exts != null ? exts :
Collections.emptyList());
encodedLen = this.length();
}
/**
* Construct an {@code OCSPStatusRequest} object from data read from
* a {@code HandshakeInputStream}
*
* @param s the {@code HandshakeInputStream} providing the encoded data
*
* @throws IOException if any decoding errors happen during object
* construction.
*/
OCSPStatusRequest(HandshakeInStream in) throws IOException {
responderIds = new ArrayList<>();
extensions = new ArrayList<>();
int ridListBytesRemaining = in.getInt16();
while (ridListBytesRemaining != 0) {
byte[] ridBytes = in.getBytes16();
responderIds.add(new ResponderId(ridBytes));
ridListBytesRemaining -= (ridBytes.length + 2);
// Make sure that no individual responder ID's length caused an
// overrun relative to the outer responder ID list length
if (ridListBytesRemaining < 0) {
throw new SSLException("Responder ID length overflow: " +
"current rid = " + ridBytes.length + ", remaining = " +
ridListBytesRemaining);
}
}
int extensionLength = in.getInt16();
if (extensionLength > 0) {
byte[] extensionData = new byte[extensionLength];
in.read(extensionData);
DerInputStream dis = new DerInputStream(extensionData);
DerValue[] extSeqContents = dis.getSequence(extensionData.length);
for (DerValue extDerVal : extSeqContents) {
extensions.add(new sun.security.x509.Extension(extDerVal));
}
}
}
/**
* Construct an {@code OCSPStatusRequest} from its encoded form
*
* @param requestBytes the status request extension bytes
*
* @throws IOException if any error occurs during decoding
*/
OCSPStatusRequest(byte[] requestBytes) throws IOException {
responderIds = new ArrayList<>();
extensions = new ArrayList<>();
ByteBuffer reqBuf = ByteBuffer.wrap(requestBytes);
// Get the ResponderId list length
encodedLen = requestBytes.length;
ridListLen = Short.toUnsignedInt(reqBuf.getShort());
int endOfRidList = reqBuf.position() + ridListLen;
// The end position of the ResponderId list in the ByteBuffer
// should be at least 2 less than the end of the buffer. This
// 2 byte defecit is the minimum length required to encode a
// zero-length extensions segment.
if (reqBuf.limit() - endOfRidList < 2) {
throw new SSLException
("ResponderId List length exceeds provided buffer - Len: "
+ ridListLen + ", Buffer: " + reqBuf.remaining());
}
while (reqBuf.position() < endOfRidList) {
int ridLength = Short.toUnsignedInt(reqBuf.getShort());
// Make sure an individual ResponderId length doesn't
// run past the end of the ResponderId list portion of the
// provided buffer.
if (reqBuf.position() + ridLength > endOfRidList) {
throw new SSLException
("ResponderId length exceeds list length - Off: "
+ reqBuf.position() + ", Length: " + ridLength
+ ", End offset: " + endOfRidList);
}
// Consume/add the ResponderId
if (ridLength > 0) {
byte[] ridData = new byte[ridLength];
reqBuf.get(ridData);
responderIds.add(new ResponderId(ridData));
}
}
// Get the Extensions length
int extensionsLen = Short.toUnsignedInt(reqBuf.getShort());
// The end of the extensions should also be the end of the
// encoded OCSPStatusRequest
if (extensionsLen != reqBuf.remaining()) {
throw new SSLException("Incorrect extensions length: Read "
+ extensionsLen + ", Data length: " + reqBuf.remaining());
}
// Extensions are a SEQUENCE of Extension
if (extensionsLen > 0) {
byte[] extensionData = new byte[extensionsLen];
reqBuf.get(extensionData);
DerInputStream dis = new DerInputStream(extensionData);
DerValue[] extSeqContents = dis.getSequence(extensionData.length);
for (DerValue extDerVal : extSeqContents) {
extensions.add(new sun.security.x509.Extension(extDerVal));
}
}
}
/**
* Obtain the length of the {@code OCSPStatusRequest} object in its
* encoded form
*
* @return the length of the {@code OCSPStatusRequest} object in its
* encoded form
*/
@Override
public int length() {
// If we've previously calculated encodedLen simply return it
if (encodedLen != 0) {
return encodedLen;
}
ridListLen = 0;
for (ResponderId rid : responderIds) {
ridListLen += rid.length() + 2;
}
extListLen = 0;
if (!extensions.isEmpty()) {
try {
DerOutputStream extSequence = new DerOutputStream();
DerOutputStream extEncoding = new DerOutputStream();
for (Extension ext : extensions) {
ext.encode(extEncoding);
}
extSequence.write(DerValue.tag_Sequence, extEncoding);
extListLen = extSequence.size();
} catch (IOException ioe) {
// Not sure what to do here
}
}
// Total length is the responder ID list length and extensions length
// plus each lists' 2-byte length fields.
encodedLen = ridListLen + extListLen + 4;
return encodedLen;
}
/**
* Send the encoded {@code OCSPStatusRequest} out through the provided
* {@code HandshakeOutputStream}
*
* @param s the {@code HandshakeOutputStream} on which to send the encoded
* data
*
* @throws IOException if any encoding errors occur
*/
@Override
public void send(HandshakeOutStream s) throws IOException {
s.putInt16(ridListLen);
for (ResponderId rid : responderIds) {
s.putBytes16(rid.getEncoded());
}
DerOutputStream seqOut = new DerOutputStream();
DerOutputStream extBytes = new DerOutputStream();
if (extensions.size() > 0) {
for (Extension ext : extensions) {
ext.encode(extBytes);
}
seqOut.write(DerValue.tag_Sequence, extBytes);
}
s.putBytes16(seqOut.toByteArray());
}
/**
* Determine if a provided {@code OCSPStatusRequest} objects is equal to
* this one.
*
* @param obj an {@code OCSPStatusRequest} object to be compared against
*
* @return {@code true} if the objects are equal, {@code false} otherwise.
* Equivalence is established if the lists of responder IDs and
* extensions between the two objects are also equal.
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
} else if (this == obj) {
return true;
} else if (obj instanceof OCSPStatusRequest) {
OCSPStatusRequest respObj = (OCSPStatusRequest)obj;
return responderIds.equals(respObj.getResponderIds()) &&
extensions.equals(respObj.getExtensions());
}
return false;
}
/**
* Returns the hash code value for this {@code OCSPStatusRequest}
*
* @return the hash code value for this {@code OCSPStatusRequest}
*/
@Override
public int hashCode() {
int result = 17;
result = 31 * result + responderIds.hashCode();
result = 31 * result + extensions.hashCode();
return result;
}
/**
* Create a string representation of this {@code OCSPStatusRequest}
*
* @return a string representation of this {@code OCSPStatusRequest}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("OCSPStatusRequest\n");
sb.append(" ResponderIds:");
if (responderIds.isEmpty()) {
sb.append(" <EMPTY>");
} else {
for (ResponderId rid : responderIds) {
sb.append("\n ").append(rid.toString());
}
}
sb.append("\n").append(" Extensions:");
if (extensions.isEmpty()) {
sb.append(" <EMPTY>");
} else {
for (Extension ext : extensions) {
sb.append("\n ").append(ext.toString());
}
}
return sb.toString();
}
/**
* Get the list of {@code ResponderId} objects for this
* {@code OCSPStatusRequest}
*
* @return an unmodifiable {@code List} of {@code ResponderId} objects
*/
List<ResponderId> getResponderIds() {
return Collections.unmodifiableList(responderIds);
}
/**
* Get the list of {@code Extension} objects for this
* {@code OCSPStatusRequest}
*
* @return an unmodifiable {@code List} of {@code Extension} objects
*/
List<Extension> getExtensions() {
return Collections.unmodifiableList(extensions);
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -65,6 +65,8 @@ public abstract class SSLContextImpl extends SSLContextSpi {
// DTLS cookie exchange manager // DTLS cookie exchange manager
private HelloCookieManager helloCookieManager; private HelloCookieManager helloCookieManager;
private StatusResponseManager statusResponseManager;
SSLContextImpl() { SSLContextImpl() {
ephemeralKeyManager = new EphemeralKeyManager(); ephemeralKeyManager = new EphemeralKeyManager();
clientCache = new SSLSessionContextImpl(); clientCache = new SSLSessionContextImpl();
@ -88,6 +90,7 @@ public abstract class SSLContextImpl extends SSLContextSpi {
} }
} }
trustManager = chooseTrustManager(tm); trustManager = chooseTrustManager(tm);
statusResponseManager = new StatusResponseManager();
if (sr == null) { if (sr == null) {
secureRandom = JsseJce.getSecureRandom(); secureRandom = JsseJce.getSecureRandom();
@ -256,6 +259,10 @@ public abstract class SSLContextImpl extends SSLContextSpi {
"Cookie exchange applies to DTLS only"); "Cookie exchange applies to DTLS only");
} }
StatusResponseManager getStatusResponseManager() {
return statusResponseManager;
}
abstract SSLParameters getDefaultServerSSLParams(); abstract SSLParameters getDefaultServerSSLParams();
abstract SSLParameters getDefaultClientSSLParams(); abstract SSLParameters getDefaultClientSSLParams();
abstract SSLParameters getSupportedSSLParams(); abstract SSLParameters getSupportedSSLParams();

View file

@ -108,6 +108,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
private String[] localSupportedSignAlgs; private String[] localSupportedSignAlgs;
private String[] peerSupportedSignAlgs; private String[] peerSupportedSignAlgs;
private List<SNIServerName> requestedServerNames; private List<SNIServerName> requestedServerNames;
private List<byte[]> statusResponses;
private int negotiatedMaxFragLen; private int negotiatedMaxFragLen;
private int maximumPacketSize; private int maximumPacketSize;
@ -180,6 +181,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
localSupportedSignAlgs = localSupportedSignAlgs =
SignatureAndHashAlgorithm.getAlgorithmNames(algorithms); SignatureAndHashAlgorithm.getAlgorithmNames(algorithms);
negotiatedMaxFragLen = -1; negotiatedMaxFragLen = -1;
statusResponses = null;
if (debug != null && Debug.isOn("session")) { if (debug != null && Debug.isOn("session")) {
System.out.println("%% Initialized: " + this); System.out.println("%% Initialized: " + this);
@ -225,6 +227,19 @@ final class SSLSessionImpl extends ExtendedSSLSession {
this.requestedServerNames = new ArrayList<>(requestedServerNames); this.requestedServerNames = new ArrayList<>(requestedServerNames);
} }
/**
* Provide status response data obtained during the SSL handshake.
*
* @param responses a {@link List} of responses in binary form.
*/
void setStatusResponses(List<byte[]> responses) {
if (responses != null && !responses.isEmpty()) {
statusResponses = responses;
} else {
statusResponses = Collections.emptyList();
}
}
/** /**
* Set the peer principal. * Set the peer principal.
*/ */
@ -531,6 +546,30 @@ final class SSLSessionImpl extends ExtendedSSLSession {
} }
} }
/**
* Return a List of status responses presented by the peer.
* Note: This method can be used only when using certificate-based
* server authentication; otherwise an empty {@code List} will be returned.
*
* @return an unmodifiable {@code List} of byte arrays, each consisting
* of a DER-encoded OCSP response (see RFC 6960). If no responses have
* been presented by the server or non-certificate based server
* authentication is used then an empty {@code List} is returned.
*/
@Override
public List<byte[]> getStatusResponses() {
if (statusResponses == null || statusResponses.isEmpty()) {
return Collections.emptyList();
} else {
// Clone both the list and the contents
List<byte[]> responses = new ArrayList<>(statusResponses.size());
for (byte[] respBytes : statusResponses) {
responses.add(respBytes.clone());
}
return Collections.unmodifiableList(responses);
}
}
/** /**
* Returns the identity of the peer which was established as part of * Returns the identity of the peer which was established as part of
* defining the session. * defining the session.

View file

@ -28,6 +28,7 @@ package sun.security.ssl;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import java.security.*; import java.security.*;
import java.security.cert.*; import java.security.cert.*;
import java.security.interfaces.*; import java.security.interfaces.*;
@ -36,9 +37,9 @@ import java.math.BigInteger;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.*; import javax.net.ssl.*;
import sun.security.action.GetLongAction;
import sun.security.util.KeyUtil; import sun.security.util.KeyUtil;
import sun.security.util.LegacyAlgorithmConstraints; import sun.security.util.LegacyAlgorithmConstraints;
import sun.security.action.GetPropertyAction; import sun.security.action.GetPropertyAction;
@ -57,11 +58,16 @@ import static sun.security.ssl.CipherSuite.KeyExchange.*;
*/ */
final class ServerHandshaker extends Handshaker { final class ServerHandshaker extends Handshaker {
// The default number of milliseconds the handshaker will wait for
// revocation status responses.
private static final long DEFAULT_STATUS_RESP_DELAY = 5000;
// is the server going to require the client to authenticate? // is the server going to require the client to authenticate?
private ClientAuthType doClientAuth; private ClientAuthType doClientAuth;
// our authentication info // our authentication info
private X509Certificate[] certs; private X509Certificate[] certs;
private Map<X509Certificate, byte[]> responseMap;
private PrivateKey privateKey; private PrivateKey privateKey;
private Object serviceCreds; private Object serviceCreds;
@ -112,6 +118,13 @@ final class ServerHandshaker extends Handshaker {
LegacyAlgorithmConstraints.PROPERTY_TLS_LEGACY_ALGS, LegacyAlgorithmConstraints.PROPERTY_TLS_LEGACY_ALGS,
new SSLAlgorithmDecomposer()); new SSLAlgorithmDecomposer());
// To switch off the status_request[_v2] extensions
private final static boolean enableStatusRequestExtension =
Debug.getBooleanProperty(
"jdk.tls.server.enableStatusRequestExtension", false);
private boolean staplingActive = false;
private long statusRespTimeout;
static { static {
String property = AccessController.doPrivileged( String property = AccessController.doPrivileged(
new GetPropertyAction("jdk.tls.ephemeralDHKeySize")); new GetPropertyAction("jdk.tls.ephemeralDHKeySize"));
@ -159,6 +172,11 @@ final class ServerHandshaker extends Handshaker {
activeProtocolVersion, isInitialHandshake, secureRenegotiation, activeProtocolVersion, isInitialHandshake, secureRenegotiation,
clientVerifyData, serverVerifyData); clientVerifyData, serverVerifyData);
doClientAuth = clientAuth; doClientAuth = clientAuth;
statusRespTimeout = AccessController.doPrivileged(
new GetLongAction("jdk.tls.stapling.responseTimeout",
DEFAULT_STATUS_RESP_DELAY));
statusRespTimeout = statusRespTimeout >= 0 ? statusRespTimeout :
DEFAULT_STATUS_RESP_DELAY;
} }
/* /*
@ -176,6 +194,11 @@ final class ServerHandshaker extends Handshaker {
activeProtocolVersion, isInitialHandshake, secureRenegotiation, activeProtocolVersion, isInitialHandshake, secureRenegotiation,
clientVerifyData, serverVerifyData, isDTLS); clientVerifyData, serverVerifyData, isDTLS);
doClientAuth = clientAuth; doClientAuth = clientAuth;
statusRespTimeout = AccessController.doPrivileged(
new GetLongAction("jdk.tls.stapling.responseTimeout",
DEFAULT_STATUS_RESP_DELAY));
statusRespTimeout = statusRespTimeout >= 0 ? statusRespTimeout :
DEFAULT_STATUS_RESP_DELAY;
} }
/* /*
@ -529,6 +552,16 @@ final class ServerHandshaker extends Handshaker {
} }
} }
// Check if the client has asserted the status_request[_v2] extension(s)
CertStatusReqExtension statReqExt = (CertStatusReqExtension)
mesg.extensions.get(ExtensionType.EXT_STATUS_REQUEST);
CertStatusReqListV2Extension statReqExtV2 =
(CertStatusReqListV2Extension)mesg.extensions.get(
ExtensionType.EXT_STATUS_REQUEST_V2);
// Keep stapling active if at least one of the extensions has been set
staplingActive = enableStatusRequestExtension &&
(statReqExt != null || statReqExtV2 != null);
/* /*
* FIRST, construct the ServerHello using the options and priorities * FIRST, construct the ServerHello using the options and priorities
* from the ClientHello. Update the (pending) cipher spec as we do * from the ClientHello. Update the (pending) cipher spec as we do
@ -825,6 +858,69 @@ final class ServerHandshaker extends Handshaker {
m1.extensions.add(maxFragLenExt); m1.extensions.add(maxFragLenExt);
} }
StatusRequestType statReqType = null;
StatusRequest statReqData = null;
if (staplingActive && !resumingSession) {
ExtensionType statusRespExt = ExtensionType.EXT_STATUS_REQUEST;
// Determine which type of stapling we are doing and assert the
// proper extension in the server hello.
// Favor status_request_v2 over status_request and ocsp_multi
// over ocsp.
// If multiple ocsp or ocsp_multi types exist, select the first
// instance of a given type
if (statReqExtV2 != null) { // RFC 6961 stapling
statusRespExt = ExtensionType.EXT_STATUS_REQUEST_V2;
List<CertStatusReqItemV2> reqItems =
statReqExtV2.getRequestItems();
int ocspIdx = -1;
int ocspMultiIdx = -1;
for (int pos = 0; pos < reqItems.size(); pos++) {
CertStatusReqItemV2 item = reqItems.get(pos);
if (ocspIdx < 0 && item.getType() ==
StatusRequestType.OCSP) {
ocspIdx = pos;
} else if (ocspMultiIdx < 0 && item.getType() ==
StatusRequestType.OCSP_MULTI) {
ocspMultiIdx = pos;
}
}
if (ocspMultiIdx >= 0) {
statReqType = reqItems.get(ocspMultiIdx).getType();
statReqData = reqItems.get(ocspMultiIdx).getRequest();
} else if (ocspIdx >= 0) {
statReqType = reqItems.get(ocspIdx).getType();
statReqData = reqItems.get(ocspIdx).getRequest();
} else {
// Some unknown type. We will not do stapling for
// this connection since we cannot understand the
// requested type.
staplingActive = false;
}
} else { // RFC 6066 stapling
statReqType = StatusRequestType.OCSP;
statReqData = statReqExt.getRequest();
}
if (statReqType != null && statReqData != null) {
// Next, attempt to obtain status responses
StatusResponseManager statRespMgr =
sslContext.getStatusResponseManager();
responseMap = statRespMgr.get(statReqType, statReqData, certs,
statusRespTimeout, TimeUnit.MILLISECONDS);
if (!responseMap.isEmpty()) {
// We now can safely assert status_request[_v2] in our
// ServerHello, and know for certain that we can provide
// responses back to this client for this connection.
if (statusRespExt == ExtensionType.EXT_STATUS_REQUEST) {
m1.extensions.add(new CertStatusReqExtension());
} else if (statusRespExt == ExtensionType.EXT_STATUS_REQUEST_V2) {
m1.extensions.add(new CertStatusReqListV2Extension());
}
}
}
}
if (debug != null && Debug.isOn("handshake")) { if (debug != null && Debug.isOn("handshake")) {
m1.print(System.out); m1.print(System.out);
System.out.println("Cipher suite: " + session.getSuite()); System.out.println("Cipher suite: " + session.getSuite());
@ -886,6 +982,32 @@ final class ServerHandshaker extends Handshaker {
} }
} }
/**
* The CertificateStatus message ... only if it is needed.
* This would only be needed if we've established that this handshake
* supports status stapling and there is at least one response to
* return to the client.
*/
if (staplingActive && !responseMap.isEmpty()) {
try {
CertificateStatus csMsg = new CertificateStatus(statReqType,
certs, responseMap);
if (debug != null && Debug.isOn("handshake")) {
csMsg.print(System.out);
}
csMsg.write(output);
handshakeState.update(csMsg, resumingSession);
responseMap = null;
} catch (SSLException ssle) {
// We don't want the exception to be fatal, we just won't
// send the message if we fail on construction.
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Failed during CertificateStatus " +
"construction: " + ssle);
}
}
}
/* /*
* THIRD, the ServerKeyExchange message ... iff it's needed. * THIRD, the ServerKeyExchange message ... iff it's needed.
* *

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
/*
* RFC 6066 defines the TLS extension,"status_request" (type 0x5),
* which allows the client to request that the server perform OCSP
* on the client's behalf.
*
* This class is an interface for multiple types of StatusRequests
* (e.g. OCSPStatusRequest).
*/
interface StatusRequest {
/**
* Obtain the length of the {@code StatusRequest} object in encoded form
*
* @return the length of the {@code StatusRequest} object in encoded form
*/
int length();
/**
* Place the encoded {@code StatusRequest} bytes into the
* {@code HandshakeOutputStream}
*
* @param s the target {@code HandshakeOutputStream}
*
* @throws IOException if any encoding error occurs
*/
void send(HandshakeOutStream s) throws IOException;
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.util.ArrayList;
import java.util.List;
final class StatusRequestType {
final int id;
final String name;
static List<StatusRequestType> knownTypes = new ArrayList<>(4);
private StatusRequestType(int id, String name) {
this.id = id;
this.name = name;
}
static StatusRequestType get(int id) {
for (StatusRequestType ext : knownTypes) {
if (ext.id == id) {
return ext;
}
}
return new StatusRequestType(id, "type_" + id);
}
private static StatusRequestType e(int id, String name) {
StatusRequestType ext = new StatusRequestType(id, name);
knownTypes.add(ext);
return ext;
}
@Override
public String toString() {
return (name == null || name.isEmpty()) ?
String.format("Unknown (0x%04X", id) : name;
}
// Status request types defined in RFC 6066 and 6961
final static StatusRequestType OCSP = e(0x01, "ocsp");
final static StatusRequestType OCSP_MULTI = e(0x02, "ocsp_multi");
}

View file

@ -0,0 +1,669 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.AccessController;
import java.security.cert.X509Certificate;
import java.security.cert.Extension;
import java.util.*;
import java.util.concurrent.*;
import sun.security.provider.certpath.CertId;
import sun.security.provider.certpath.OCSP;
import sun.security.provider.certpath.OCSPResponse;
import sun.security.provider.certpath.ResponderId;
import sun.security.util.Cache;
import sun.security.x509.PKIXExtensions;
import sun.security.x509.SerialNumber;
import sun.security.action.GetBooleanAction;
import sun.security.action.GetIntegerAction;
import sun.security.action.GetPropertyAction;
final class StatusResponseManager {
private static final int DEFAULT_CORE_THREADS = 8;
private static final int DEFAULT_CACHE_SIZE = 256;
private static final int DEFAULT_CACHE_LIFETIME = 3600; // seconds
private static final Debug debug = Debug.getInstance("ssl");
private final ScheduledThreadPoolExecutor threadMgr;
private final Cache<CertId, ResponseCacheEntry> responseCache;
private final URI defaultResponder;
private final boolean respOverride;
private final int cacheCapacity;
private final int cacheLifetime;
private final boolean ignoreExtensions;
/**
* Create a StatusResponseManager with default parameters.
*/
StatusResponseManager() {
int cap = AccessController.doPrivileged(
new GetIntegerAction("jdk.tls.stapling.cacheSize",
DEFAULT_CACHE_SIZE));
cacheCapacity = cap > 0 ? cap : 0;
int life = AccessController.doPrivileged(
new GetIntegerAction("jdk.tls.stapling.cacheLifetime",
DEFAULT_CACHE_LIFETIME));
cacheLifetime = life > 0 ? life : 0;
String uriStr = AccessController.doPrivileged(
new GetPropertyAction("jdk.tls.stapling.responderURI"));
URI tmpURI;
try {
tmpURI = ((uriStr != null && !uriStr.isEmpty()) ?
new URI(uriStr) : null);
} catch (URISyntaxException urise) {
tmpURI = null;
}
defaultResponder = tmpURI;
respOverride = AccessController.doPrivileged(
new GetBooleanAction("jdk.tls.stapling.responderOverride"));
ignoreExtensions = AccessController.doPrivileged(
new GetBooleanAction("jdk.tls.stapling.ignoreExtensions"));
threadMgr = new ScheduledThreadPoolExecutor(DEFAULT_CORE_THREADS,
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
});
threadMgr.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
threadMgr.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
threadMgr.setKeepAliveTime(5000, TimeUnit.MILLISECONDS);
responseCache = Cache.newSoftMemoryCache(cacheCapacity, cacheLifetime);
}
/**
* Get the current cache lifetime setting
*
* @return the current cache lifetime value
*/
int getCacheLifetime() {
return cacheLifetime;
}
/**
* Get the current maximum cache size.
*
* @return the current maximum cache size
*/
int getCacheCapacity() {
return cacheCapacity;
}
/**
* Get the default OCSP responder URI, if previously set.
*
* @return the current default OCSP responder URI, or {@code null} if
* it has not been set.
*/
URI getDefaultResponder() {
return defaultResponder;
}
/**
* Get the URI override setting
*
* @return {@code true} if URI override has been set, {@code false}
* otherwise.
*/
boolean getURIOverride() {
return respOverride;
}
/**
* Get the ignore extensions setting.
*
* @return {@code true} if the {@code StatusResponseManager} will not
* pass OCSP Extensions in the TLS {@code status_request[_v2]} extensions,
* {@code false} if extensions will be passed (the default).
*/
boolean getIgnoreExtensions() {
return ignoreExtensions;
}
/**
* Clear the status response cache
*/
void clear() {
debugLog("Clearing response cache");
responseCache.clear();
}
/**
* Returns the number of currently valid objects in the response cache.
*
* @return the number of valid objects in the response cache.
*/
int size() {
return responseCache.size();
}
/**
* Obtain the URI use by the {@code StatusResponseManager} during lookups.
* This method takes into account not only the AIA extension from a
* certificate to be checked, but also any default URI and possible
* override settings for the response manager.
*
* @param cert the subject to get the responder URI from
*
* @return a {@code URI} containing the address to the OCSP responder, or
* {@code null} if no AIA extension exists in the certificate and no
* default responder has been configured.
*
* @throws NullPointerException if {@code cert} is {@code null}.
*/
URI getURI(X509Certificate cert) {
Objects.requireNonNull(cert);
if (cert.getExtensionValue(
PKIXExtensions.OCSPNoCheck_Id.toString()) != null) {
debugLog("OCSP NoCheck extension found. OCSP will be skipped");
return null;
} else if (defaultResponder != null && respOverride) {
debugLog("Responder override: URI is " + defaultResponder);
return defaultResponder;
} else {
URI certURI = OCSP.getResponderURI(cert);
return (certURI != null ? certURI : defaultResponder);
}
}
/**
* Shutdown the thread pool
*/
void shutdown() {
debugLog("Shutting down " + threadMgr.getActiveCount() +
" active threads");
threadMgr.shutdown();
}
/**
* Get a list of responses for a chain of certificates.
* This will find OCSP responses from the cache, or failing that, directly
* contact the OCSP responder. It is assumed that the certificates in
* the provided chain are in their proper order (from end-entity to
* trust anchor).
*
* @param type the type of request being made of the
* {@code StatusResponseManager}
* @param request the {@code StatusRequest} from the status_request or
* status_request_v2 ClientHello extension. A value of {@code null}
* is interpreted as providing no responder IDs or extensions.
* @param chain an array of 2 or more certificates. Each certificate must
* be issued by the next certificate in the chain.
* @param delay the number of time units to delay before returning
* responses.
* @param unit the unit of time applied to the {@code delay} parameter
*
* @return an unmodifiable {@code Map} containing the certificate and
* its usually
*
* @throws SSLHandshakeException if an unsupported {@code StatusRequest}
* is provided.
*/
Map<X509Certificate, byte[]> get(StatusRequestType type,
StatusRequest request, X509Certificate[] chain, long delay,
TimeUnit unit) {
Map<X509Certificate, byte[]> responseMap = new HashMap<>();
List<OCSPFetchCall> requestList = new ArrayList<>();
debugLog("Beginning check: Type = " + type + ", Chain length = " +
chain.length);
// It is assumed that the caller has ordered the certs in the chain
// in the proper order (each certificate is issued by the next entry
// in the provided chain).
if (chain.length < 2) {
return Collections.emptyMap();
}
if (type == StatusRequestType.OCSP) {
try {
// For type OCSP, we only check the end-entity certificate
OCSPStatusRequest ocspReq = (OCSPStatusRequest)request;
CertId cid = new CertId(chain[1],
new SerialNumber(chain[0].getSerialNumber()));
ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq);
if (cacheEntry != null) {
responseMap.put(chain[0], cacheEntry.ocspBytes);
} else {
StatusInfo sInfo = new StatusInfo(chain[0], cid);
requestList.add(new OCSPFetchCall(sInfo, ocspReq));
}
} catch (IOException exc) {
debugLog("Exception during CertId creation: " + exc);
}
} else if (type == StatusRequestType.OCSP_MULTI) {
// For type OCSP_MULTI, we check every cert in the chain that
// has a direct issuer at the next index. We won't have an issuer
// certificate for the last certificate in the chain and will
// not be able to create a CertId because of that.
OCSPStatusRequest ocspReq = (OCSPStatusRequest)request;
int ctr;
for (ctr = 0; ctr < chain.length - 1; ctr++) {
try {
// The cert at "ctr" is the subject cert, "ctr + 1" is the
// issuer certificate.
CertId cid = new CertId(chain[ctr + 1],
new SerialNumber(chain[ctr].getSerialNumber()));
ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq);
if (cacheEntry != null) {
responseMap.put(chain[ctr], cacheEntry.ocspBytes);
} else {
StatusInfo sInfo = new StatusInfo(chain[ctr], cid);
requestList.add(new OCSPFetchCall(sInfo, ocspReq));
}
} catch (IOException exc) {
debugLog("Exception during CertId creation: " + exc);
}
}
} else {
debugLog("Unsupported status request type: " + type);
}
// If we were able to create one or more Fetches, go and run all
// of them in separate threads. For all the threads that completed
// in the allotted time, put those status responses into the returned
// Map.
if (!requestList.isEmpty()) {
try {
// Set a bunch of threads to go do the fetching
List<Future<StatusInfo>> resultList =
threadMgr.invokeAll(requestList, delay, unit);
// Go through the Futures and from any non-cancelled task,
// get the bytes and attach them to the responseMap.
for (Future<StatusInfo> task : resultList) {
if (task.isDone()) {
if (!task.isCancelled()) {
StatusInfo info = task.get();
if (info != null && info.responseData != null) {
responseMap.put(info.cert,
info.responseData.ocspBytes);
} else {
debugLog("Completed task had no response data");
}
} else {
debugLog("Found cancelled task");
}
}
}
} catch (InterruptedException | ExecutionException exc) {
// Not sure what else to do here
debugLog("Exception when getting data: " + exc);
}
}
return Collections.unmodifiableMap(responseMap);
}
/**
* Check the cache for a given {@code CertId}.
*
* @param cid the CertId of the response to look up
* @param ocspRequest the OCSP request structure sent by the client
* in the TLS status_request[_v2] hello extension.
*
* @return the {@code ResponseCacheEntry} for a specific CertId, or
* {@code null} if it is not found or a nonce extension has been
* requested by the caller.
*/
private ResponseCacheEntry getFromCache(CertId cid,
OCSPStatusRequest ocspRequest) {
// Determine if the nonce extension is present in the request. If
// so, then do not attempt to retrieve the response from the cache.
for (Extension ext : ocspRequest.getExtensions()) {
if (ext.getId().equals(PKIXExtensions.OCSPNonce_Id.toString())) {
debugLog("Nonce extension found, skipping cache check");
return null;
}
}
ResponseCacheEntry respEntry = responseCache.get(cid);
// If the response entry has a nextUpdate and it has expired
// before the cache expiration, purge it from the cache
// and do not return it as a cache hit.
if (respEntry != null && respEntry.nextUpdate != null &&
respEntry.nextUpdate.before(new Date())) {
debugLog("nextUpdate threshold exceeded, purging from cache");
respEntry = null;
}
debugLog("Check cache for SN" + cid.getSerialNumber() + ": " +
(respEntry != null ? "HIT" : "MISS"));
return respEntry;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("StatusResponseManager: ");
sb.append("Core threads: ").append(threadMgr.getCorePoolSize());
sb.append(", Cache timeout: ");
if (cacheLifetime > 0) {
sb.append(cacheLifetime).append(" seconds");
} else {
sb.append(" indefinite");
}
sb.append(", Cache MaxSize: ");
if (cacheCapacity > 0) {
sb.append(cacheCapacity).append(" items");
} else {
sb.append(" unbounded");
}
sb.append(", Default URI: ");
if (defaultResponder != null) {
sb.append(defaultResponder);
} else {
sb.append("NONE");
}
return sb.toString();
}
/**
* Log messages through the SSL Debug facility.
*
* @param message the message to be displayed
*/
static void debugLog(String message) {
if (debug != null && Debug.isOn("respmgr")) {
StringBuilder sb = new StringBuilder();
sb.append("[").append(Thread.currentThread().getName());
sb.append("] ").append(message);
System.out.println(sb.toString());
}
}
/**
* Inner class used to group request and response data.
*/
class StatusInfo {
final X509Certificate cert;
final CertId cid;
final URI responder;
ResponseCacheEntry responseData;
/**
* Create a StatusInfo object from certificate data.
*
* @param subjectCert the certificate to be checked for revocation
* @param issuerCert the issuer of the {@code subjectCert}
*
* @throws IOException if CertId creation from the certificates fails
*/
StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert)
throws IOException {
this(subjectCert, new CertId(issuerCert,
new SerialNumber(subjectCert.getSerialNumber())));
}
/**
* Create a StatusInfo object from an existing subject certificate
* and its corresponding CertId.
*
* @param subjectCert the certificate to be checked for revocation
* @param cid the CertId for {@code subjectCert}
*/
StatusInfo(X509Certificate subjectCert, CertId certId) {
cert = subjectCert;
cid = certId;
responder = getURI(cert);
responseData = null;
}
/**
* Copy constructor (used primarily for rescheduling).
* This will do a member-wise copy with the exception of the
* responseData and extensions fields, which should not persist
* in a rescheduled fetch.
*
* @param orig the original {@code StatusInfo}
*/
StatusInfo(StatusInfo orig) {
this.cert = orig.cert;
this.cid = orig.cid;
this.responder = orig.responder;
this.responseData = null;
}
/**
* Return a String representation of the {@code StatusInfo}
*
* @return a {@code String} representation of this object
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("StatusInfo:");
sb.append("\n\tCert: ").append(this.cert.getSubjectX500Principal());
sb.append("\n\tSerial: ").append(this.cert.getSerialNumber());
sb.append("\n\tResponder: ").append(this.responder);
sb.append("\n\tResponse data: ").append(this.responseData != null ?
(this.responseData.ocspBytes.length + " bytes") : "<NULL>");
return sb.toString();
}
}
/**
* Static nested class used as the data kept in the response cache.
*/
static class ResponseCacheEntry {
final OCSPResponse.ResponseStatus status;
final byte[] ocspBytes;
final Date nextUpdate;
final OCSPResponse.SingleResponse singleResp;
final ResponderId respId;
/**
* Create a new cache entry from the raw bytes of the response
*
* @param responseBytes the DER encoding for the OCSP response
*
* @throws IOException if an {@code OCSPResponse} cannot be created from
* the encoded bytes.
*/
ResponseCacheEntry(byte[] responseBytes, CertId cid)
throws IOException {
Objects.requireNonNull(responseBytes,
"Non-null responseBytes required");
Objects.requireNonNull(cid, "Non-null Cert ID required");
ocspBytes = responseBytes.clone();
OCSPResponse oResp = new OCSPResponse(ocspBytes);
status = oResp.getResponseStatus();
respId = oResp.getResponderId();
singleResp = oResp.getSingleResponse(cid);
if (status == OCSPResponse.ResponseStatus.SUCCESSFUL) {
if (singleResp != null) {
// Pull out the nextUpdate field in advance because the
// Date is cloned.
nextUpdate = singleResp.getNextUpdate();
} else {
throw new IOException("Unable to find SingleResponse for " +
"SN " + cid.getSerialNumber());
}
} else {
nextUpdate = null;
}
}
}
/**
* Inner Callable class that does the actual work of looking up OCSP
* responses, first looking at the cache and doing OCSP requests if
* a cache miss occurs.
*/
class OCSPFetchCall implements Callable<StatusInfo> {
StatusInfo statInfo;
OCSPStatusRequest ocspRequest;
List<Extension> extensions;
List<ResponderId> responderIds;
/**
* A constructor that builds the OCSPFetchCall from the provided
* StatusInfo and information from the status_request[_v2] extension.
*
* @param info the {@code StatusInfo} containing the subject
* certificate, CertId, and other supplemental info.
* @param request the {@code OCSPStatusRequest} containing any
* responder IDs and extensions.
*/
public OCSPFetchCall(StatusInfo info, OCSPStatusRequest request) {
statInfo = Objects.requireNonNull(info,
"Null StatusInfo not allowed");
ocspRequest = Objects.requireNonNull(request,
"Null OCSPStatusRequest not allowed");
extensions = ocspRequest.getExtensions();
responderIds = ocspRequest.getResponderIds();
}
/**
* Get an OCSP response, either from the cache or from a responder.
*
* @return The StatusInfo object passed into the {@code OCSPFetchCall}
* constructor, with the {@code responseData} field filled in with the
* response or {@code null} if no response can be obtained.
*/
@Override
public StatusInfo call() {
debugLog("Starting fetch for SN " + statInfo.cid.getSerialNumber());
try {
ResponseCacheEntry cacheEntry;
List<Extension> extsToSend;
if (statInfo.responder == null) {
// If we have no URI then there's nothing to do but return
debugLog("Null URI detected, OCSP fetch aborted.");
return statInfo;
} else {
debugLog("Attempting fetch from " + statInfo.responder);
}
// If the StatusResponseManager has been configured to not
// forward extensions, then set extensions to an empty list.
// We will forward the extensions unless one of two conditions
// occur: (1) The jdk.tls.stapling.ignoreExtensions property is
// true or (2) There is a non-empty ResponderId list.
// ResponderId selection is a feature that will be
// supported in the future.
extsToSend = (ignoreExtensions || !responderIds.isEmpty()) ?
Collections.emptyList() : extensions;
byte[] respBytes = OCSP.getOCSPBytes(
Collections.singletonList(statInfo.cid),
statInfo.responder, extsToSend);
if (respBytes != null) {
// Place the data into the response cache
cacheEntry = new ResponseCacheEntry(respBytes,
statInfo.cid);
// Get the response status and act on it appropriately
debugLog("OCSP Status: " + cacheEntry.status +
" (" + respBytes.length + " bytes)");
if (cacheEntry.status ==
OCSPResponse.ResponseStatus.SUCCESSFUL) {
// Set the response in the returned StatusInfo
statInfo.responseData = cacheEntry;
// Add the response to the cache (if applicable)
addToCache(statInfo.cid, cacheEntry);
}
} else {
debugLog("No data returned from OCSP Responder");
}
} catch (IOException ioe) {
debugLog("Caught exception: " + ioe);
}
return statInfo;
}
/**
* Add a response to the cache.
*
* @param certId The {@code CertId} for the OCSP response
* @param entry A cache entry containing the response bytes and
* the {@code OCSPResponse} built from those bytes.
*/
private void addToCache(CertId certId, ResponseCacheEntry entry) {
// If no cache lifetime has been set on entries then
// don't cache this response if there is no nextUpdate field
if (entry.nextUpdate == null && cacheLifetime == 0) {
debugLog("Not caching this OCSP response");
} else {
responseCache.put(certId, entry);
debugLog("Added response for SN " + certId.getSerialNumber() +
" to cache");
}
}
/**
* Determine the delay to use when scheduling the task that will
* update the OCSP response. This is the shorter time between the
* cache lifetime and the nextUpdate. If no nextUpdate is present in
* the response, then only the cache lifetime is used.
* If cache timeouts are disabled (a zero value) and there's no
* nextUpdate, then the entry is not cached and no rescheduling will
* take place.
*
* @param nextUpdate a {@code Date} object corresponding to the
* next update time from a SingleResponse.
*
* @return the number of seconds of delay before the next fetch
* should be executed. A zero value means that the fetch
* should happen immediately, while a value less than zero
* indicates no rescheduling should be done.
*/
private long getNextTaskDelay(Date nextUpdate) {
long delaySec;
int lifetime = getCacheLifetime();
if (nextUpdate != null) {
long nuDiffSec = (nextUpdate.getTime() -
System.currentTimeMillis()) / 1000;
delaySec = lifetime > 0 ? Long.min(nuDiffSec, lifetime) :
nuDiffSec;
} else {
delaySec = lifetime > 0 ? lifetime : -1;
}
return delaySec;
}
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
final class UnknownStatusRequest implements StatusRequest {
private final byte[] data;
UnknownStatusRequest(HandshakeInStream s, int len) throws IOException {
data = new byte[len];
if (len > 0) {
s.read(data);
}
}
UnknownStatusRequest(byte[] requestBytes) {
data = requestBytes;
}
@Override
public int length() {
return data.length;
}
@Override
public void send(HandshakeOutStream s) throws IOException {
// A raw write of the data
s.write(data);
}
@Override
public String toString() {
return "Unsupported StatusRequest, data: " +
Debug.toString(data);
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -184,6 +184,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager
Validator v = checkTrustedInit(chain, authType, isClient); Validator v = checkTrustedInit(chain, authType, isClient);
AlgorithmConstraints constraints = null; AlgorithmConstraints constraints = null;
List<byte[]> responseList = Collections.emptyList();
if ((socket != null) && socket.isConnected() && if ((socket != null) && socket.isConnected() &&
(socket instanceof SSLSocket)) { (socket instanceof SSLSocket)) {
@ -204,7 +205,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager
// create the algorithm constraints // create the algorithm constraints
ProtocolVersion protocolVersion = ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol()); ProtocolVersion.valueOf(session.getProtocol());
if (protocolVersion.useTLS12PlusSpec()) { if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
if (session instanceof ExtendedSSLSession) { if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession = ExtendedSSLSession extSession =
(ExtendedSSLSession)session; (ExtendedSSLSession)session;
@ -220,13 +221,21 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager
} else { } else {
constraints = new SSLAlgorithmConstraints(sslSocket, false); constraints = new SSLAlgorithmConstraints(sslSocket, false);
} }
// Grab any stapled OCSP responses for use in validation
if (session instanceof ExtendedSSLSession) {
responseList =
((ExtendedSSLSession)session).getStatusResponses();
}
} }
X509Certificate[] trustedChain = null; X509Certificate[] trustedChain = null;
if (isClient) { if (isClient) {
trustedChain = validate(v, chain, constraints, null); trustedChain = validate(v, chain, Collections.emptyList(),
constraints, null);
} else { } else {
trustedChain = validate(v, chain, constraints, authType); trustedChain = validate(v, chain, responseList, constraints,
authType);
} }
if (debug != null && Debug.isOn("trustmanager")) { if (debug != null && Debug.isOn("trustmanager")) {
System.out.println("Found trusted certificate:"); System.out.println("Found trusted certificate:");
@ -239,6 +248,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager
Validator v = checkTrustedInit(chain, authType, isClient); Validator v = checkTrustedInit(chain, authType, isClient);
AlgorithmConstraints constraints = null; AlgorithmConstraints constraints = null;
List<byte[]> responseList = Collections.emptyList();
if (engine != null) { if (engine != null) {
SSLSession session = engine.getHandshakeSession(); SSLSession session = engine.getHandshakeSession();
if (session == null) { if (session == null) {
@ -256,7 +266,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager
// create the algorithm constraints // create the algorithm constraints
ProtocolVersion protocolVersion = ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol()); ProtocolVersion.valueOf(session.getProtocol());
if (protocolVersion.useTLS12PlusSpec()) { if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
if (session instanceof ExtendedSSLSession) { if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession = ExtendedSSLSession extSession =
(ExtendedSSLSession)session; (ExtendedSSLSession)session;
@ -272,13 +282,21 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager
} else { } else {
constraints = new SSLAlgorithmConstraints(engine, false); constraints = new SSLAlgorithmConstraints(engine, false);
} }
// Grab any stapled OCSP responses for use in validation
if (session instanceof ExtendedSSLSession) {
responseList =
((ExtendedSSLSession)session).getStatusResponses();
}
} }
X509Certificate[] trustedChain = null; X509Certificate[] trustedChain = null;
if (isClient) { if (isClient) {
trustedChain = validate(v, chain, constraints, null); trustedChain = validate(v, chain, Collections.emptyList(),
constraints, null);
} else { } else {
trustedChain = validate(v, chain, constraints, authType); trustedChain = validate(v, chain, responseList, constraints,
authType);
} }
if (debug != null && Debug.isOn("trustmanager")) { if (debug != null && Debug.isOn("trustmanager")) {
System.out.println("Found trusted certificate:"); System.out.println("Found trusted certificate:");
@ -317,11 +335,12 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager
} }
private static X509Certificate[] validate(Validator v, private static X509Certificate[] validate(Validator v,
X509Certificate[] chain, AlgorithmConstraints constraints, X509Certificate[] chain, List<byte[]> responseList,
String authType) throws CertificateException { AlgorithmConstraints constraints, String authType)
throws CertificateException {
Object o = JsseJce.beginFipsProvider(); Object o = JsseJce.beginFipsProvider();
try { try {
return v.validate(chain, null, constraints, authType); return v.validate(chain, null, responseList, constraints, authType);
} finally { } finally {
JsseJce.endFipsProvider(o); JsseJce.endFipsProvider(o);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -186,6 +186,7 @@ public final class PKIXValidator extends Validator {
@Override @Override
X509Certificate[] engineValidate(X509Certificate[] chain, X509Certificate[] engineValidate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts, Collection<X509Certificate> otherCerts,
List<byte[]> responseList,
AlgorithmConstraints constraints, AlgorithmConstraints constraints,
Object parameter) throws CertificateException { Object parameter) throws CertificateException {
if ((chain == null) || (chain.length == 0)) { if ((chain == null) || (chain.length == 0)) {
@ -202,6 +203,11 @@ public final class PKIXValidator extends Validator {
pkixParameters.addCertPathChecker(algorithmChecker); pkixParameters.addCertPathChecker(algorithmChecker);
} }
// attach it to the PKIXBuilderParameters.
if (!responseList.isEmpty()) {
addResponses(pkixParameters, chain, responseList);
}
// check that chain is in correct order and check if chain contains // check that chain is in correct order and check if chain contains
// trust anchor // trust anchor
X500Principal prevIssuer = null; X500Principal prevIssuer = null;
@ -369,4 +375,70 @@ public final class PKIXValidator extends Validator {
("PKIX path building failed: " + e.toString(), e); ("PKIX path building failed: " + e.toString(), e);
} }
} }
/**
* For OCSP Stapling, add responses that came in during the handshake
* into a {@code PKIXRevocationChecker} so we can evaluate them.
*
* @param pkixParams the pkixParameters object that will be used in
* path validation.
* @param chain the chain of certificates to verify
* @param responseList a {@code List} of zero or more byte arrays, each
* one being a DER-encoded OCSP response (per RFC 6960). Entries
* in the List must match the order of the certificates in the
* chain parameter.
*/
private static void addResponses(PKIXBuilderParameters pkixParams,
X509Certificate[] chain, List<byte[]> responseList) {
if (pkixParams.isRevocationEnabled()) {
try {
// Make a modifiable copy of the CertPathChecker list
PKIXRevocationChecker revChecker = null;
List<PKIXCertPathChecker> checkerList =
new ArrayList<>(pkixParams.getCertPathCheckers());
// Find the first PKIXRevocationChecker in the list
for (PKIXCertPathChecker checker : checkerList) {
if (checker instanceof PKIXRevocationChecker) {
revChecker = (PKIXRevocationChecker)checker;
break;
}
}
// If we still haven't found one, make one
if (revChecker == null) {
revChecker = (PKIXRevocationChecker)CertPathValidator.
getInstance("PKIX").getRevocationChecker();
checkerList.add(revChecker);
}
// Each response in the list should be in parallel with
// the certificate list. If there is a zero-length response
// treat it as being absent. If the user has provided their
// own PKIXRevocationChecker with pre-populated responses, do
// not overwrite them with the ones from the handshake.
Map<X509Certificate, byte[]> responseMap =
revChecker.getOcspResponses();
int limit = Integer.min(chain.length, responseList.size());
for (int idx = 0; idx < limit; idx++) {
byte[] respBytes = responseList.get(idx);
if (respBytes != null && respBytes.length > 0 &&
!responseMap.containsKey(chain[idx])) {
responseMap.put(chain[idx], respBytes);
}
}
// Add the responses and push it all back into the
// PKIXBuilderParameters
revChecker.setOcspResponses(responseMap);
pkixParams.setCertPathCheckers(checkerList);
} catch (NoSuchAlgorithmException exc) {
// This should not occur, but if it does happen then
// stapled OCSP responses won't be part of revocation checking.
// Clients can still fall back to other means of revocation
// checking.
}
}
}
} }

View file

@ -122,6 +122,7 @@ public final class SimpleValidator extends Validator {
@Override @Override
X509Certificate[] engineValidate(X509Certificate[] chain, X509Certificate[] engineValidate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts, Collection<X509Certificate> otherCerts,
List<byte[]> responseList,
AlgorithmConstraints constraints, AlgorithmConstraints constraints,
Object parameter) throws CertificateException { Object parameter) throws CertificateException {
if ((chain == null) || (chain.length == 0)) { if ((chain == null) || (chain.length == 0)) {

View file

@ -235,7 +235,8 @@ public abstract class Validator {
public final X509Certificate[] validate(X509Certificate[] chain, public final X509Certificate[] validate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts, Object parameter) Collection<X509Certificate> otherCerts, Object parameter)
throws CertificateException { throws CertificateException {
return validate(chain, otherCerts, null, parameter); return validate(chain, otherCerts, Collections.emptyList(), null,
parameter);
} }
/** /**
@ -244,6 +245,13 @@ public abstract class Validator {
* @param chain the target certificate chain * @param chain the target certificate chain
* @param otherCerts a Collection of additional X509Certificates that * @param otherCerts a Collection of additional X509Certificates that
* could be helpful for path building (or null) * could be helpful for path building (or null)
* @param responseList a List of zero or more byte arrays, each
* one being a DER-encoded OCSP response (per RFC 6960). Entries
* in the List must match the order of the certificates in the
* chain parameter. It is possible that fewer responses may be
* in the list than are elements in {@code chain} and a missing
* response for a matching element in {@code chain} can be
* represented with a zero-length byte array.
* @param constraints algorithm constraints for certification path * @param constraints algorithm constraints for certification path
* processing * processing
* @param parameter an additional parameter with variant specific meaning. * @param parameter an additional parameter with variant specific meaning.
@ -257,9 +265,11 @@ public abstract class Validator {
*/ */
public final X509Certificate[] validate(X509Certificate[] chain, public final X509Certificate[] validate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts, Collection<X509Certificate> otherCerts,
List<byte[]> responseList,
AlgorithmConstraints constraints, AlgorithmConstraints constraints,
Object parameter) throws CertificateException { Object parameter) throws CertificateException {
chain = engineValidate(chain, otherCerts, constraints, parameter); chain = engineValidate(chain, otherCerts, responseList, constraints,
parameter);
// omit EE extension check if EE cert is also trust anchor // omit EE extension check if EE cert is also trust anchor
if (chain.length > 1) { if (chain.length > 1) {
@ -280,6 +290,7 @@ public abstract class Validator {
abstract X509Certificate[] engineValidate(X509Certificate[] chain, abstract X509Certificate[] engineValidate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts, Collection<X509Certificate> otherCerts,
List<byte[]> responseList,
AlgorithmConstraints constraints, AlgorithmConstraints constraints,
Object parameter) throws CertificateException; Object parameter) throws CertificateException;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -77,6 +77,11 @@ public class PKIXExtensions {
private static final int[] OCSPNoCheck_data = { 1, 3, 6, 1, 5, 5, 7, private static final int[] OCSPNoCheck_data = { 1, 3, 6, 1, 5, 5, 7,
48, 1, 5}; 48, 1, 5};
// Additional extensions under the PKIX arc that are not necessarily
// used in X.509 Certificates or CRLs.
private static final int OCSPNonce_data [] = { 1, 3, 6, 1, 5, 5, 7,
48, 1, 2};
/** /**
* Identifies the particular public key used to sign the certificate. * Identifies the particular public key used to sign the certificate.
*/ */
@ -104,18 +109,20 @@ public class PKIXExtensions {
public static final ObjectIdentifier CertificatePolicies_Id; public static final ObjectIdentifier CertificatePolicies_Id;
/** /**
* Lists pairs of objectidentifiers of policies considered equivalent by the * Lists pairs of object identifiers of policies considered equivalent by
* issuing CA to the subject CA. * the issuing CA to the subject CA.
*/ */
public static final ObjectIdentifier PolicyMappings_Id; public static final ObjectIdentifier PolicyMappings_Id;
/** /**
* Allows additional identities to be bound to the subject of the certificate. * Allows additional identities to be bound to the subject of the
* certificate.
*/ */
public static final ObjectIdentifier SubjectAlternativeName_Id; public static final ObjectIdentifier SubjectAlternativeName_Id;
/** /**
* Allows additional identities to be associated with the certificate issuer. * Allows additional identities to be associated with the certificate
* issuer.
*/ */
public static final ObjectIdentifier IssuerAlternativeName_Id; public static final ObjectIdentifier IssuerAlternativeName_Id;
@ -224,6 +231,12 @@ public class PKIXExtensions {
*/ */
public static final ObjectIdentifier OCSPNoCheck_Id; public static final ObjectIdentifier OCSPNoCheck_Id;
/**
* This extension is used to provide nonce data for OCSP requests
* or responses.
*/
public static final ObjectIdentifier OCSPNonce_Id;
static { static {
AuthorityKey_Id = ObjectIdentifier.newInternal(AuthorityKey_data); AuthorityKey_Id = ObjectIdentifier.newInternal(AuthorityKey_data);
SubjectKey_Id = ObjectIdentifier.newInternal(SubjectKey_data); SubjectKey_Id = ObjectIdentifier.newInternal(SubjectKey_data);
@ -266,5 +279,6 @@ public class PKIXExtensions {
ObjectIdentifier.newInternal(SubjectInfoAccess_data); ObjectIdentifier.newInternal(SubjectInfoAccess_data);
FreshestCRL_Id = ObjectIdentifier.newInternal(FreshestCRL_data); FreshestCRL_Id = ObjectIdentifier.newInternal(FreshestCRL_data);
OCSPNoCheck_Id = ObjectIdentifier.newInternal(OCSPNoCheck_data); OCSPNoCheck_Id = ObjectIdentifier.newInternal(OCSPNoCheck_data);
OCSPNonce_Id = ObjectIdentifier.newInternal(OCSPNonce_data);
} }
} }

View file

@ -0,0 +1,539 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.testlibrary;
import java.io.*;
import java.util.*;
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.Extension;
import javax.security.auth.x500.X500Principal;
import java.math.BigInteger;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AccessDescription;
import sun.security.x509.AlgorithmId;
import sun.security.x509.AuthorityInfoAccessExtension;
import sun.security.x509.AuthorityKeyIdentifierExtension;
import sun.security.x509.SubjectKeyIdentifierExtension;
import sun.security.x509.BasicConstraintsExtension;
import sun.security.x509.ExtendedKeyUsageExtension;
import sun.security.x509.DNSName;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.KeyUsageExtension;
import sun.security.x509.SerialNumber;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.URIName;
import sun.security.x509.KeyIdentifier;
/**
* Helper class that builds and signs X.509 certificates.
*
* A CertificateBuilder is created with a default constructor, and then
* uses additional public methods to set the public key, desired validity
* dates, serial number and extensions. It is expected that the caller will
* have generated the necessary key pairs prior to using a CertificateBuilder
* to generate certificates.
*
* The following methods are mandatory before calling build():
* <UL>
* <LI>{@link #setSubjectName(java.lang.String)}
* <LI>{@link #setPublicKey(java.security.PublicKey)}
* <LI>{@link #setNotBefore(java.util.Date)} and
* {@link #setNotAfter(java.util.Date)}, or
* {@link #setValidity(java.util.Date, java.util.Date)}
* <LI>{@link #setSerialNumber(java.math.BigInteger)}
* </UL><BR>
*
* Additionally, the caller can either provide a {@link List} of
* {@link Extension} objects, or use the helper classes to add specific
* extension types.
*
* When all required and desired parameters are set, the
* {@link #build(java.security.cert.X509Certificate, java.security.PrivateKey,
* java.lang.String)} method can be used to create the {@link X509Certificate}
* object.
*
* Multiple certificates may be cut from the same settings using subsequent
* calls to the build method. Settings may be cleared using the
* {@link #reset()} method.
*/
public class CertificateBuilder {
private final CertificateFactory factory;
private X500Principal subjectName = null;
private BigInteger serialNumber = null;
private PublicKey publicKey = null;
private Date notBefore = null;
private Date notAfter = null;
private final Map<String, Extension> extensions = new HashMap<>();
private byte[] tbsCertBytes;
private byte[] signatureBytes;
/**
* Default constructor for a {@code CertificateBuilder} object.
*
* @throws CertificateException if the underlying {@link CertificateFactory}
* cannot be instantiated.
*/
public CertificateBuilder() throws CertificateException {
factory = CertificateFactory.getInstance("X.509");
}
/**
* Set the subject name for the certificate.
*
* @param name An {@link X500Principal} to be used as the subject name
* on this certificate.
*/
public void setSubjectName(X500Principal name) {
subjectName = name;
}
/**
* Set the subject name for the certificate.
*
* @param name The subject name in RFC 2253 format
*/
public void setSubjectName(String name) {
subjectName = new X500Principal(name);
}
/**
* Set the public key for this certificate.
*
* @param pubKey The {@link PublicKey} to be used on this certificate.
*/
public void setPublicKey(PublicKey pubKey) {
publicKey = Objects.requireNonNull(pubKey, "Caught null public key");
}
/**
* Set the NotBefore date on the certificate.
*
* @param nbDate A {@link Date} object specifying the start of the
* certificate validity period.
*/
public void setNotBefore(Date nbDate) {
Objects.requireNonNull(nbDate, "Caught null notBefore date");
notBefore = (Date)nbDate.clone();
}
/**
* Set the NotAfter date on the certificate.
*
* @param naDate A {@link Date} object specifying the end of the
* certificate validity period.
*/
public void setNotAfter(Date naDate) {
Objects.requireNonNull(naDate, "Caught null notAfter date");
notAfter = (Date)naDate.clone();
}
/**
* Set the validity period for the certificate
*
* @param nbDate A {@link Date} object specifying the start of the
* certificate validity period.
* @param naDate A {@link Date} object specifying the end of the
* certificate validity period.
*/
public void setValidity(Date nbDate, Date naDate) {
setNotBefore(nbDate);
setNotAfter(naDate);
}
/**
* Set the serial number on the certificate.
*
* @param serial A serial number in {@link BigInteger} form.
*/
public void setSerialNumber(BigInteger serial) {
Objects.requireNonNull(serial, "Caught null serial number");
serialNumber = serial;
}
/**
* Add a single extension to the certificate.
*
* @param ext The extension to be added.
*/
public void addExtension(Extension ext) {
Objects.requireNonNull(ext, "Caught null extension");
extensions.put(ext.getId(), ext);
}
/**
* Add multiple extensions contained in a {@code List}.
*
* @param extList The {@link List} of extensions to be added to
* the certificate.
*/
public void addExtensions(List<Extension> extList) {
Objects.requireNonNull(extList, "Caught null extension list");
for (Extension ext : extList) {
extensions.put(ext.getId(), ext);
}
}
/**
* Helper method to add DNSName types for the SAN extension
*
* @param dnsNames A {@code List} of names to add as DNSName types
*
* @throws IOException if an encoding error occurs.
*/
public void addSubjectAltNameDNSExt(List<String> dnsNames) throws IOException {
if (!dnsNames.isEmpty()) {
GeneralNames gNames = new GeneralNames();
for (String name : dnsNames) {
gNames.add(new GeneralName(new DNSName(name)));
}
addExtension(new SubjectAlternativeNameExtension(false,
gNames));
}
}
/**
* Helper method to add one or more OCSP URIs to the Authority Info Access
* certificate extension.
*
* @param locations A list of one or more OCSP responder URIs as strings
*
* @throws IOException if an encoding error occurs.
*/
public void addAIAExt(List<String> locations)
throws IOException {
if (!locations.isEmpty()) {
List<AccessDescription> acDescList = new ArrayList<>();
for (String ocspUri : locations) {
acDescList.add(new AccessDescription(
AccessDescription.Ad_OCSP_Id,
new GeneralName(new URIName(ocspUri))));
}
addExtension(new AuthorityInfoAccessExtension(acDescList));
}
}
/**
* Set a Key Usage extension for the certificate. The extension will
* be marked critical.
*
* @param bitSettings Boolean array for all nine bit settings in the order
* documented in RFC 5280 section 4.2.1.3.
*
* @throws IOException if an encoding error occurs.
*/
public void addKeyUsageExt(boolean[] bitSettings) throws IOException {
addExtension(new KeyUsageExtension(bitSettings));
}
/**
* Set the Basic Constraints Extension for a certificate.
*
* @param crit {@code true} if critical, {@code false} otherwise
* @param isCA {@code true} if the extension will be on a CA certificate,
* {@code false} otherwise
* @param maxPathLen The maximum path length issued by this CA. Values
* less than zero will omit this field from the resulting extension and
* no path length constraint will be asserted.
*
* @throws IOException if an encoding error occurs.
*/
public void addBasicConstraintsExt(boolean crit, boolean isCA,
int maxPathLen) throws IOException {
addExtension(new BasicConstraintsExtension(crit, isCA, maxPathLen));
}
/**
* Add the Authority Key Identifier extension.
*
* @param authorityCert The certificate of the issuing authority.
*
* @throws IOException if an encoding error occurs.
*/
public void addAuthorityKeyIdExt(X509Certificate authorityCert)
throws IOException {
addAuthorityKeyIdExt(authorityCert.getPublicKey());
}
/**
* Add the Authority Key Identifier extension.
*
* @param authorityKey The public key of the issuing authority.
*
* @throws IOException if an encoding error occurs.
*/
public void addAuthorityKeyIdExt(PublicKey authorityKey) throws IOException {
KeyIdentifier kid = new KeyIdentifier(authorityKey);
addExtension(new AuthorityKeyIdentifierExtension(kid, null, null));
}
/**
* Add the Subject Key Identifier extension.
*
* @param subjectKey The public key to be used in the resulting certificate
*
* @throws IOException if an encoding error occurs.
*/
public void addSubjectKeyIdExt(PublicKey subjectKey) throws IOException {
byte[] keyIdBytes = new KeyIdentifier(subjectKey).getIdentifier();
addExtension(new SubjectKeyIdentifierExtension(keyIdBytes));
}
/**
* Add the Extended Key Usage extension.
*
* @param ekuOids A {@link List} of object identifiers in string form.
*
* @throws IOException if an encoding error occurs.
*/
public void addExtendedKeyUsageExt(List<String> ekuOids)
throws IOException {
if (!ekuOids.isEmpty()) {
Vector<ObjectIdentifier> oidVector = new Vector<>();
for (String oid : ekuOids) {
oidVector.add(new ObjectIdentifier(oid));
}
addExtension(new ExtendedKeyUsageExtension(oidVector));
}
}
/**
* Clear all settings and return the {@code CertificateBuilder} to
* its default state.
*/
public void reset() {
extensions.clear();
subjectName = null;
notBefore = null;
notAfter = null;
serialNumber = null;
publicKey = null;
signatureBytes = null;
tbsCertBytes = null;
}
/**
* Build the certificate.
*
* @param issuerCert The certificate of the issuing authority, or
* {@code null} if the resulting certificate is self-signed.
* @param issuerKey The private key of the issuing authority
* @param algName The signature algorithm name
*
* @return The resulting {@link X509Certificate}
*
* @throws IOException if an encoding error occurs.
* @throws CertificateException If the certificate cannot be generated
* by the underlying {@link CertificateFactory}
* @throws NoSuchAlgorithmException If an invalid signature algorithm
* is provided.
*/
public X509Certificate build(X509Certificate issuerCert,
PrivateKey issuerKey, String algName)
throws IOException, CertificateException, NoSuchAlgorithmException {
// TODO: add some basic checks (key usage, basic constraints maybe)
AlgorithmId signAlg = AlgorithmId.get(algName);
byte[] encodedCert = encodeTopLevel(issuerCert, issuerKey, signAlg);
ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert);
return (X509Certificate)factory.generateCertificate(bais);
}
/**
* Encode the contents of the outer-most ASN.1 SEQUENCE:
*
* <PRE>
* Certificate ::= SEQUENCE {
* tbsCertificate TBSCertificate,
* signatureAlgorithm AlgorithmIdentifier,
* signatureValue BIT STRING }
* </PRE>
*
* @param issuerCert The certificate of the issuing authority, or
* {@code null} if the resulting certificate is self-signed.
* @param issuerKey The private key of the issuing authority
* @param signAlg The signature algorithm object
*
* @return The DER-encoded X.509 certificate
*
* @throws CertificateException If an error occurs during the
* signing process.
* @throws IOException if an encoding error occurs.
*/
private byte[] encodeTopLevel(X509Certificate issuerCert,
PrivateKey issuerKey, AlgorithmId signAlg)
throws CertificateException, IOException {
DerOutputStream outerSeq = new DerOutputStream();
DerOutputStream topLevelItems = new DerOutputStream();
tbsCertBytes = encodeTbsCert(issuerCert, signAlg);
topLevelItems.write(tbsCertBytes);
try {
signatureBytes = signCert(issuerKey, signAlg);
} catch (GeneralSecurityException ge) {
throw new CertificateException(ge);
}
signAlg.derEncode(topLevelItems);
topLevelItems.putBitString(signatureBytes);
outerSeq.write(DerValue.tag_Sequence, topLevelItems);
return outerSeq.toByteArray();
}
/**
* Encode the bytes for the TBSCertificate structure:
* <PRE>
* TBSCertificate ::= SEQUENCE {
* version [0] EXPLICIT Version DEFAULT v1,
* serialNumber CertificateSerialNumber,
* signature AlgorithmIdentifier,
* issuer Name,
* validity Validity,
* subject Name,
* subjectPublicKeyInfo SubjectPublicKeyInfo,
* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
* -- If present, version MUST be v2 or v3
* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
* -- If present, version MUST be v2 or v3
* extensions [3] EXPLICIT Extensions OPTIONAL
* -- If present, version MUST be v3
* }
*
* @param issuerCert The certificate of the issuing authority, or
* {@code null} if the resulting certificate is self-signed.
* @param signAlg The signature algorithm object
*
* @return The DER-encoded bytes for the TBSCertificate structure
*
* @throws IOException if an encoding error occurs.
*/
private byte[] encodeTbsCert(X509Certificate issuerCert,
AlgorithmId signAlg) throws IOException {
DerOutputStream tbsCertSeq = new DerOutputStream();
DerOutputStream tbsCertItems = new DerOutputStream();
// Hardcode to V3
byte[] v3int = {0x02, 0x01, 0x02};
tbsCertItems.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,
(byte)0), v3int);
// Serial Number
SerialNumber sn = new SerialNumber(serialNumber);
sn.encode(tbsCertItems);
// Algorithm ID
signAlg.derEncode(tbsCertItems);
// Issuer Name
if (issuerCert != null) {
tbsCertItems.write(
issuerCert.getSubjectX500Principal().getEncoded());
} else {
// Self-signed
tbsCertItems.write(subjectName.getEncoded());
}
// Validity period (set as UTCTime)
DerOutputStream valSeq = new DerOutputStream();
valSeq.putUTCTime(notBefore);
valSeq.putUTCTime(notAfter);
tbsCertItems.write(DerValue.tag_Sequence, valSeq);
// Subject Name
tbsCertItems.write(subjectName.getEncoded());
// SubjectPublicKeyInfo
tbsCertItems.write(publicKey.getEncoded());
// TODO: Extensions!
encodeExtensions(tbsCertItems);
// Wrap it all up in a SEQUENCE and return the bytes
tbsCertSeq.write(DerValue.tag_Sequence, tbsCertItems);
return tbsCertSeq.toByteArray();
}
/**
* Encode the extensions segment for an X.509 Certificate:
*
* <PRE>
* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
*
* Extension ::= SEQUENCE {
* extnID OBJECT IDENTIFIER,
* critical BOOLEAN DEFAULT FALSE,
* extnValue OCTET STRING
* -- contains the DER encoding of an ASN.1 value
* -- corresponding to the extension type identified
* -- by extnID
* }
* </PRE>
*
* @param tbsStream The {@code DerOutputStream} that holds the
* TBSCertificate contents.
*
* @throws IOException if an encoding error occurs.
*/
private void encodeExtensions(DerOutputStream tbsStream)
throws IOException {
DerOutputStream extSequence = new DerOutputStream();
DerOutputStream extItems = new DerOutputStream();
for (Extension ext : extensions.values()) {
ext.encode(extItems);
}
extSequence.write(DerValue.tag_Sequence, extItems);
tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,
(byte)3), extSequence);
}
/**
* Digitally sign the X.509 certificate.
*
* @param issuerKey The private key of the issuing authority
* @param signAlg The signature algorithm object
*
* @return The digital signature bytes.
*
* @throws GeneralSecurityException If any errors occur during the
* digital signature process.
*/
private byte[] signCert(PrivateKey issuerKey, AlgorithmId signAlg)
throws GeneralSecurityException {
Signature sig = Signature.getInstance(signAlg.getName());
sig.initSign(issuerKey);
sig.update(tbsCertBytes);
return sig.sign();
}
}

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,8 @@
* @bug 8043758 * @bug 8043758
* @summary Datagram Transport Layer Security (DTLS) * @summary Datagram Transport Layer Security (DTLS)
* @compile DTLSOverDatagram.java * @compile DTLSOverDatagram.java
* @run main/othervm NoMacInitialClientHello * @run main/othervm -Djdk.tls.client.enableStatusRequestExtension=false
* NoMacInitialClientHello
*/ */
import java.net.DatagramPacket; import java.net.DatagramPacket;

View file

@ -0,0 +1,797 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// SunJSSE does not support dynamic system properties, no way to re-use
// system properties in samevm/agentvm mode.
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS
* @library ../../../../java/security/testlibrary
* @build CertificateBuilder SimpleOCSPServer
* @run main/othervm HttpsUrlConnClient
*/
import java.io.*;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.net.Socket;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import javax.net.ssl.*;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.Security;
import java.security.GeneralSecurityException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.Certificate;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.security.cert.PKIXRevocationChecker;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import sun.security.testlibrary.SimpleOCSPServer;
import sun.security.testlibrary.CertificateBuilder;
import sun.security.validator.ValidatorException;
public class HttpsUrlConnClient {
/*
* =============================================================
* Set the various variables needed for the tests, then
* specify what tests to run on each side.
*/
static final byte[] LINESEP = { 10 };
static final Base64.Encoder B64E = Base64.getMimeEncoder(64, LINESEP);
// Turn on TLS debugging
static boolean debug = true;
/*
* Should we run the client or server in a separate thread?
* Both sides can throw exceptions, but do you have a preference
* as to which side should be the main thread.
*/
static boolean separateServerThread = true;
Thread clientThread = null;
Thread serverThread = null;
static String passwd = "passphrase";
static String ROOT_ALIAS = "root";
static String INT_ALIAS = "intermediate";
static String SSL_ALIAS = "ssl";
/*
* Is the server ready to serve?
*/
volatile static boolean serverReady = false;
volatile int serverPort = 0;
volatile Exception serverException = null;
volatile Exception clientException = null;
// PKI components we will need for this test
static KeyStore rootKeystore; // Root CA Keystore
static KeyStore intKeystore; // Intermediate CA Keystore
static KeyStore serverKeystore; // SSL Server Keystore
static KeyStore trustStore; // SSL Client trust store
static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder
static int rootOcspPort; // Port number for root OCSP
static SimpleOCSPServer intOcsp; // Intermediate CA OCSP Responder
static int intOcspPort; // Port number for intermed. OCSP
private static final String SIMPLE_WEB_PAGE = "<HTML>\n" +
"<HEAD><Title>Web Page!</Title></HEAD>\n" +
"<BODY><H1>Web Page!</H1></BODY>\n</HTML>";
private static final SimpleDateFormat utcDateFmt =
new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z");
/*
* If the client or server is doing some kind of object creation
* that the other side depends on, and that thread prematurely
* exits, you may experience a hang. The test harness will
* terminate all hung threads after its timeout has expired,
* currently 3 minutes by default, but you might try to be
* smart about it....
*/
public static void main(String[] args) throws Exception {
if (debug) {
System.setProperty("javax.net.debug", "ssl");
}
System.setProperty("javax.net.ssl.keyStore", "");
System.setProperty("javax.net.ssl.keyStorePassword", "");
System.setProperty("javax.net.ssl.trustStore", "");
System.setProperty("javax.net.ssl.trustStorePassword", "");
// Create the PKI we will use for the test and start the OCSP servers
createPKI();
utcDateFmt.setTimeZone(TimeZone.getTimeZone("GMT"));
testPKIXParametersRevEnabled();
// shut down the OCSP responders before finishing the test
intOcsp.stop();
rootOcsp.stop();
}
/**
* Do a basic connection using PKIXParameters with revocation checking
* enabled and client-side OCSP disabled. It will only pass if all
* stapled responses are present, valid and have a GOOD status.
*/
static void testPKIXParametersRevEnabled() throws Exception {
ClientParameters cliParams = new ClientParameters();
ServerParameters servParams = new ServerParameters();
serverReady = false;
System.out.println("=====================================");
System.out.println("Stapling enabled, PKIXParameters with");
System.out.println("Revocation checking enabled ");
System.out.println("=====================================");
// Set the certificate entry in the intermediate OCSP responder
// with a revocation date of 8 hours ago.
X509Certificate sslCert =
(X509Certificate)serverKeystore.getCertificate(SSL_ALIAS);
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_REVOKED,
new Date(System.currentTimeMillis() -
TimeUnit.HOURS.toMillis(8))));
intOcsp.updateStatusDb(revInfo);
// Set up revocation checking on the client with no client-side
// OCSP fall-back
cliParams.pkixParams = new PKIXBuilderParameters(trustStore,
new X509CertSelector());
cliParams.pkixParams.setRevocationEnabled(true);
Security.setProperty("ocsp.enable", "false");
HttpsUrlConnClient sslTest = new HttpsUrlConnClient(cliParams,
servParams);
TestResult tr = sslTest.getResult();
if (!checkClientValidationFailure(tr.clientExc, BasicReason.REVOKED)) {
if (tr.clientExc != null) {
throw tr.clientExc;
} else {
throw new RuntimeException(
"Expected client failure, but the client succeeded");
}
}
// In this case the server should also have thrown an exception
// because of the client alert
if (tr.serverExc instanceof SSLHandshakeException) {
if (!tr.serverExc.getMessage().contains(
"alert: bad_certificate_status_response")) {
throw tr.serverExc;
}
}
System.out.println(" PASS");
System.out.println("=====================================\n");
}
/*
* Define the server side of the test.
*
* If the server prematurely exits, serverReady will be set to true
* to avoid infinite hangs.
*/
void doServerSide(ServerParameters servParams) throws Exception {
// Selectively enable or disable the feature
System.setProperty("jdk.tls.server.enableStatusRequestExtension",
Boolean.toString(servParams.enabled));
// Set all the other operating parameters
System.setProperty("jdk.tls.stapling.cacheSize",
Integer.toString(servParams.cacheSize));
System.setProperty("jdk.tls.stapling.cacheLifetime",
Integer.toString(servParams.cacheLifetime));
System.setProperty("jdk.tls.stapling.responseTimeout",
Integer.toString(servParams.respTimeout));
System.setProperty("jdk.tls.stapling.responderURI", servParams.respUri);
System.setProperty("jdk.tls.stapling.responderOverride",
Boolean.toString(servParams.respOverride));
System.setProperty("jdk.tls.stapling.ignoreExtensions",
Boolean.toString(servParams.ignoreExts));
// Set keystores and trust stores for the server
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(serverKeystore, passwd.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslc = SSLContext.getInstance("TLS");
sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory sslssf = sslc.getServerSocketFactory();
SSLServerSocket sslServerSocket =
(SSLServerSocket) sslssf.createServerSocket(serverPort);
serverPort = sslServerSocket.getLocalPort();
log("Server Port is " + serverPort);
// Dump the private key in PKCS8 format, not encrypted. This
// key dump can be used if the traffic was captured using tcpdump
// or wireshark to look into the encrypted packets for debug purposes.
if (debug) {
byte[] keybytes = serverKeystore.getKey(SSL_ALIAS,
passwd.toCharArray()).getEncoded();
PKCS8EncodedKeySpec p8spec = new PKCS8EncodedKeySpec(keybytes);
StringBuilder keyPem = new StringBuilder();
keyPem.append("-----BEGIN PRIVATE KEY-----\n");
keyPem.append(B64E.encodeToString(p8spec.getEncoded())).append("\n");
keyPem.append("-----END PRIVATE KEY-----\n");
log("Private key is:\n" + keyPem.toString());
}
/*
* Signal Client, we're ready for his connect.
*/
serverReady = true;
try (SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(sslSocket.getInputStream()));
OutputStream out = sslSocket.getOutputStream()) {
StringBuilder hdrBldr = new StringBuilder();
String line;
while ((line = in.readLine()) != null && !line.isEmpty()) {
hdrBldr.append(line).append("\n");
}
String headerText = hdrBldr.toString();
log("Header Received: " + headerText.length() + " bytes\n" +
headerText);
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.0 200 OK\r\n");
sb.append("Date: ").append(utcDateFmt.format(new Date())).
append("\r\n");
sb.append("Content-Type: text/html\r\n");
sb.append("Content-Length: ").append(SIMPLE_WEB_PAGE.length());
sb.append("\r\n\r\n");
out.write(sb.toString().getBytes("UTF-8"));
out.write(SIMPLE_WEB_PAGE.getBytes("UTF-8"));
out.flush();
log("Server replied with:\n" + sb.toString() + SIMPLE_WEB_PAGE);
}
}
/*
* Define the client side of the test.
*
* If the server prematurely exits, serverReady will be set to true
* to avoid infinite hangs.
*/
void doClientSide(ClientParameters cliParams) throws Exception {
/*
* Wait for server to get started.
*/
while (!serverReady) {
Thread.sleep(50);
}
// Selectively enable or disable the feature
System.setProperty("jdk.tls.client.enableStatusRequestExtension",
Boolean.toString(cliParams.enabled));
HtucSSLSocketFactory sockFac = new HtucSSLSocketFactory(cliParams);
HttpsURLConnection.setDefaultSSLSocketFactory(sockFac);
URL location = new URL("https://localhost:" + serverPort);
HttpsURLConnection tlsConn =
(HttpsURLConnection)location.openConnection();
tlsConn.setConnectTimeout(5000);
tlsConn.setReadTimeout(5000);
tlsConn.setDoInput(true);
try (InputStream in = tlsConn.getInputStream()) {
// Check the response
if (debug && tlsConn.getResponseCode() !=
HttpURLConnection.HTTP_OK) {
log("Received HTTP error: " + tlsConn.getResponseCode() +
" - " + tlsConn.getResponseMessage());
throw new IOException("HTTP error: " +
tlsConn.getResponseCode());
}
int contentLength = tlsConn.getContentLength();
if (contentLength == -1) {
contentLength = Integer.MAX_VALUE;
}
byte[] response = new byte[contentLength > 2048 ? 2048 : contentLength];
int total = 0;
while (total < contentLength) {
int count = in.read(response, total, response.length - total);
if (count < 0)
break;
total += count;
log("Read " + count + " bytes (" + total + " total)");
if (total >= response.length && total < contentLength) {
response = Arrays.copyOf(response, total * 2);
}
}
response = Arrays.copyOf(response, total);
String webPage = new String(response, 0, total);
if (debug) {
log("Web page:\n" + webPage);
}
}
}
/*
* Primary constructor, used to drive remainder of the test.
*
* Fork off the other side, then do your work.
*/
HttpsUrlConnClient(ClientParameters cliParams,
ServerParameters servParams) throws Exception {
Exception startException = null;
try {
if (separateServerThread) {
startServer(servParams, true);
startClient(cliParams, false);
} else {
startClient(cliParams, true);
startServer(servParams, false);
}
} catch (Exception e) {
startException = e;
}
/*
* Wait for other side to close down.
*/
if (separateServerThread) {
if (serverThread != null) {
serverThread.join();
}
} else {
if (clientThread != null) {
clientThread.join();
}
}
}
/**
* Checks a validation failure to see if it failed for the reason we think
* it should. This comes in as an SSLException of some sort, but it
* encapsulates a ValidatorException which in turn encapsulates the
* CertPathValidatorException we are interested in.
*
* @param e the exception thrown at the top level
* @param reason the underlying CertPathValidatorException BasicReason
* we are expecting it to have.
*
* @return true if the reason matches up, false otherwise.
*/
static boolean checkClientValidationFailure(Exception e,
BasicReason reason) {
boolean result = false;
if (e instanceof SSLException) {
Throwable valExc = e.getCause();
if (valExc instanceof sun.security.validator.ValidatorException) {
Throwable cause = valExc.getCause();
if (cause instanceof CertPathValidatorException) {
CertPathValidatorException cpve =
(CertPathValidatorException)cause;
if (cpve.getReason() == reason) {
result = true;
}
}
}
}
return result;
}
TestResult getResult() {
TestResult tr = new TestResult();
tr.clientExc = clientException;
tr.serverExc = serverException;
return tr;
}
final void startServer(ServerParameters servParams, boolean newThread)
throws Exception {
if (newThread) {
serverThread = new Thread() {
@Override
public void run() {
try {
doServerSide(servParams);
} catch (Exception e) {
/*
* Our server thread just died.
*
* Release the client, if not active already...
*/
System.err.println("Server died...");
serverReady = true;
serverException = e;
}
}
};
serverThread.start();
} else {
try {
doServerSide(servParams);
} catch (Exception e) {
serverException = e;
} finally {
serverReady = true;
}
}
}
final void startClient(ClientParameters cliParams, boolean newThread)
throws Exception {
if (newThread) {
clientThread = new Thread() {
@Override
public void run() {
try {
doClientSide(cliParams);
} catch (Exception e) {
/*
* Our client thread just died.
*/
System.err.println("Client died...");
clientException = e;
}
}
};
clientThread.start();
} else {
try {
doClientSide(cliParams);
} catch (Exception e) {
clientException = e;
}
}
}
/**
* Creates the PKI components necessary for this test, including
* Root CA, Intermediate CA and SSL server certificates, the keystores
* for each entity, a client trust store, and starts the OCSP responders.
*/
private static void createPKI() throws Exception {
CertificateBuilder cbld = new CertificateBuilder();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyStore.Builder keyStoreBuilder =
KeyStore.Builder.newInstance("PKCS12", null,
new KeyStore.PasswordProtection(passwd.toCharArray()));
// Generate Root, IntCA, EE keys
KeyPair rootCaKP = keyGen.genKeyPair();
log("Generated Root CA KeyPair");
KeyPair intCaKP = keyGen.genKeyPair();
log("Generated Intermediate CA KeyPair");
KeyPair sslKP = keyGen.genKeyPair();
log("Generated SSL Cert KeyPair");
// Set up the Root CA Cert
cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany");
cbld.setPublicKey(rootCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("1"));
// Make a 3 year validity starting from 60 days ago
long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
long end = start + TimeUnit.DAYS.toMillis(1085);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
// Make our Root CA Cert!
X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Root CA Created:\n" + certInfo(rootCert));
// Now build a keystore and add the keys and cert
rootKeystore = keyStoreBuilder.getKeyStore();
Certificate[] rootChain = {rootCert};
rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(),
passwd.toCharArray(), rootChain);
// Now fire up the OCSP responder
rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null);
rootOcsp.enableLog(debug);
rootOcsp.setNextUpdateInterval(3600);
rootOcsp.start();
Thread.sleep(1000); // Give the server a second to start up
rootOcspPort = rootOcsp.getPort();
String rootRespURI = "http://localhost:" + rootOcspPort;
log("Root OCSP Responder URI is " + rootRespURI);
// Now that we have the root keystore and OCSP responder we can
// create our intermediate CA.
cbld.reset();
cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany");
cbld.setPublicKey(intCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("100"));
// Make a 2 year validity starting from 30 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30);
end = start + TimeUnit.DAYS.toMillis(730);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
cbld.addAIAExt(Collections.singletonList(rootRespURI));
// Make our Intermediate CA Cert!
X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Intermediate CA Created:\n" + certInfo(intCaCert));
// Provide intermediate CA cert revocation info to the Root CA
// OCSP responder.
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
revInfo.put(intCaCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
rootOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
intKeystore = keyStoreBuilder.getKeyStore();
Certificate[] intChain = {intCaCert, rootCert};
intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(),
passwd.toCharArray(), intChain);
intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// Now fire up the Intermediate CA OCSP responder
intOcsp = new SimpleOCSPServer(intKeystore, passwd,
INT_ALIAS, null);
intOcsp.enableLog(debug);
intOcsp.setNextUpdateInterval(3600);
intOcsp.start();
Thread.sleep(1000);
intOcspPort = intOcsp.getPort();
String intCaRespURI = "http://localhost:" + intOcspPort;
log("Intermediate CA OCSP Responder URI is " + intCaRespURI);
// Last but not least, let's make our SSLCert and add it to its own
// Keystore
cbld.reset();
cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany");
cbld.setPublicKey(sslKP.getPublic());
cbld.setSerialNumber(new BigInteger("4096"));
// Make a 1 year validity starting from 7 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
end = start + TimeUnit.DAYS.toMillis(365);
cbld.setValidity(new Date(start), new Date(end));
// Add extensions
addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic());
boolean[] kuBits = {true, false, true, false, false, false,
false, false, false};
cbld.addKeyUsageExt(kuBits);
List<String> ekuOids = new ArrayList<>();
ekuOids.add("1.3.6.1.5.5.7.3.1");
ekuOids.add("1.3.6.1.5.5.7.3.2");
cbld.addExtendedKeyUsageExt(ekuOids);
cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost"));
cbld.addAIAExt(Collections.singletonList(intCaRespURI));
// Make our SSL Server Cert!
X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(),
"SHA256withRSA");
log("SSL Certificate Created:\n" + certInfo(sslCert));
// Provide SSL server cert revocation info to the Intermeidate CA
// OCSP responder.
revInfo = new HashMap<>();
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
intOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
serverKeystore = keyStoreBuilder.getKeyStore();
Certificate[] sslChain = {sslCert, intCaCert, rootCert};
serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(),
passwd.toCharArray(), sslChain);
serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// And finally a Trust Store for the client
trustStore = keyStoreBuilder.getKeyStore();
trustStore.setCertificateEntry(ROOT_ALIAS, rootCert);
}
private static void addCommonExts(CertificateBuilder cbld,
PublicKey subjKey, PublicKey authKey) throws IOException {
cbld.addSubjectKeyIdExt(subjKey);
cbld.addAuthorityKeyIdExt(authKey);
}
private static void addCommonCAExts(CertificateBuilder cbld)
throws IOException {
cbld.addBasicConstraintsExt(true, true, -1);
// Set key usage bits for digitalSignature, keyCertSign and cRLSign
boolean[] kuBitSettings = {true, false, false, false, false, true,
true, false, false};
cbld.addKeyUsageExt(kuBitSettings);
}
/**
* Helper routine that dumps only a few cert fields rather than
* the whole toString() output.
*
* @param cert an X509Certificate to be displayed
*
* @return the String output of the issuer, subject and
* serial number
*/
private static String certInfo(X509Certificate cert) {
StringBuilder sb = new StringBuilder();
sb.append("Issuer: ").append(cert.getIssuerX500Principal()).
append("\n");
sb.append("Subject: ").append(cert.getSubjectX500Principal()).
append("\n");
sb.append("Serial: ").append(cert.getSerialNumber()).append("\n");
return sb.toString();
}
/**
* Log a message on stdout
*
* @param message The message to log
*/
private static void log(String message) {
if (debug) {
System.out.println(message);
}
}
// The following two classes are Simple nested class to group a handful
// of configuration parameters used before starting a client or server.
// We'll just access the data members directly for convenience.
static class ClientParameters {
boolean enabled = true;
PKIXBuilderParameters pkixParams = null;
PKIXRevocationChecker revChecker = null;
ClientParameters() { }
}
static class ServerParameters {
boolean enabled = true;
int cacheSize = 256;
int cacheLifetime = 3600;
int respTimeout = 5000;
String respUri = "";
boolean respOverride = false;
boolean ignoreExts = false;
ServerParameters() { }
}
static class TestResult {
Exception serverExc = null;
Exception clientExc = null;
}
static class HtucSSLSocketFactory extends SSLSocketFactory {
SSLContext sslc = SSLContext.getInstance("TLS");
HtucSSLSocketFactory(ClientParameters cliParams)
throws GeneralSecurityException {
super();
// Create the Trust Manager Factory using the PKIX variant
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
// If we have a customized pkixParameters then use it
if (cliParams.pkixParams != null) {
// LIf we have a customized PKIXRevocationChecker, add
// it to the PKIXBuilderParameters.
if (cliParams.revChecker != null) {
cliParams.pkixParams.addCertPathChecker(
cliParams.revChecker);
}
ManagerFactoryParameters trustParams =
new CertPathTrustManagerParameters(
cliParams.pkixParams);
tmf.init(trustParams);
} else {
tmf.init(trustStore);
}
sslc.init(null, tmf.getTrustManagers(), null);
}
@Override
public Socket createSocket(Socket s, String host, int port,
boolean autoClose) throws IOException {
Socket sock = sslc.getSocketFactory().createSocket(s, host, port,
autoClose);
setCiphers(sock);
return sock;
}
@Override
public Socket createSocket(InetAddress host, int port)
throws IOException {
Socket sock = sslc.getSocketFactory().createSocket(host, port);
setCiphers(sock);
return sock;
}
@Override
public Socket createSocket(InetAddress host, int port,
InetAddress localAddress, int localPort) throws IOException {
Socket sock = sslc.getSocketFactory().createSocket(host, port,
localAddress, localPort);
setCiphers(sock);
return sock;
}
@Override
public Socket createSocket(String host, int port)
throws IOException {
Socket sock = sslc.getSocketFactory().createSocket(host, port);
setCiphers(sock);
return sock;
}
@Override
public Socket createSocket(String host, int port,
InetAddress localAddress, int localPort)
throws IOException {
Socket sock = sslc.getSocketFactory().createSocket(host, port,
localAddress, localPort);
setCiphers(sock);
return sock;
}
@Override
public String[] getDefaultCipherSuites() {
return sslc.getDefaultSSLParameters().getCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return sslc.getSupportedSSLParameters().getCipherSuites();
}
private static void setCiphers(Socket sock) {
if (sock instanceof SSLSocket) {
String[] ciphers = { "TLS_RSA_WITH_AES_128_CBC_SHA" };
((SSLSocket)sock).setEnabledCipherSuites(ciphers);
}
}
}
}

View file

@ -0,0 +1,655 @@
/*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// SunJSSE does not support dynamic system properties, no way to re-use
// system properties in samevm/agentvm mode.
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS
* @library ../../../../java/security/testlibrary
* @build CertificateBuilder SimpleOCSPServer
* @run main/othervm SSLEngineWithStapling
*/
/**
* A SSLEngine usage example which simplifies the presentation
* by removing the I/O and multi-threading concerns.
*
* The test creates two SSLEngines, simulating a client and server.
* The "transport" layer consists two byte buffers: think of them
* as directly connected pipes.
*
* Note, this is a *very* simple example: real code will be much more
* involved. For example, different threading and I/O models could be
* used, transport mechanisms could close unexpectedly, and so on.
*
* When this application runs, notice that several messages
* (wrap/unwrap) pass before any application data is consumed or
* produced. (For more information, please see the SSL/TLS
* specifications.) There may several steps for a successful handshake,
* so it's typical to see the following series of operations:
*
* client server message
* ====== ====== =======
* wrap() ... ClientHello
* ... unwrap() ClientHello
* ... wrap() ServerHello/Certificate
* unwrap() ... ServerHello/Certificate
* wrap() ... ClientKeyExchange
* wrap() ... ChangeCipherSpec
* wrap() ... Finished
* ... unwrap() ClientKeyExchange
* ... unwrap() ChangeCipherSpec
* ... unwrap() Finished
* ... wrap() ChangeCipherSpec
* ... wrap() Finished
* unwrap() ... ChangeCipherSpec
* unwrap() ... Finished
*/
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.*;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.nio.*;
import java.security.cert.CertPathValidatorException;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509Certificate;
import java.security.cert.X509CertSelector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import sun.security.testlibrary.SimpleOCSPServer;
import sun.security.testlibrary.CertificateBuilder;
public class SSLEngineWithStapling {
/*
* Enables logging of the SSLEngine operations.
*/
private static final boolean logging = true;
/*
* Enables the JSSE system debugging system property:
*
* -Djavax.net.debug=all
*
* This gives a lot of low-level information about operations underway,
* including specific handshake messages, and might be best examined
* after gaining some familiarity with this application.
*/
private static final boolean debug = false;
private SSLEngine clientEngine; // client Engine
private ByteBuffer clientOut; // write side of clientEngine
private ByteBuffer clientIn; // read side of clientEngine
private SSLEngine serverEngine; // server Engine
private ByteBuffer serverOut; // write side of serverEngine
private ByteBuffer serverIn; // read side of serverEngine
/*
* For data transport, this example uses local ByteBuffers. This
* isn't really useful, but the purpose of this example is to show
* SSLEngine concepts, not how to do network transport.
*/
private ByteBuffer cTOs; // "reliable" transport client->server
private ByteBuffer sTOc; // "reliable" transport server->client
/*
* The following is to set up the keystores.
*/
static final String passwd = "passphrase";
static final String ROOT_ALIAS = "root";
static final String INT_ALIAS = "intermediate";
static final String SSL_ALIAS = "ssl";
// PKI components we will need for this test
static KeyStore rootKeystore; // Root CA Keystore
static KeyStore intKeystore; // Intermediate CA Keystore
static KeyStore serverKeystore; // SSL Server Keystore
static KeyStore trustStore; // SSL Client trust store
static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder
static int rootOcspPort; // Port number for root OCSP
static SimpleOCSPServer intOcsp; // Intermediate CA OCSP Responder
static int intOcspPort; // Port number for intermed. OCSP
/*
* Main entry point for this test.
*/
public static void main(String args[]) throws Exception {
if (debug) {
System.setProperty("javax.net.debug", "ssl");
}
// Create the PKI we will use for the test and start the OCSP servers
createPKI();
// Set the certificate entry in the intermediate OCSP responder
// with a revocation date of 8 hours ago.
X509Certificate sslCert =
(X509Certificate)serverKeystore.getCertificate(SSL_ALIAS);
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_REVOKED,
new Date(System.currentTimeMillis() -
TimeUnit.HOURS.toMillis(8))));
intOcsp.updateStatusDb(revInfo);
SSLEngineWithStapling test = new SSLEngineWithStapling();
try {
test.runTest();
throw new RuntimeException("Expected failure due to revocation " +
"did not occur");
} catch (Exception e) {
if (!checkClientValidationFailure(e,
CertPathValidatorException.BasicReason.REVOKED)) {
System.out.println("*** Didn't find the exception we wanted");
throw e;
}
}
System.out.println("Test Passed.");
}
/*
* Create an initialized SSLContext to use for these tests.
*/
public SSLEngineWithStapling() throws Exception {
System.setProperty("javax.net.ssl.keyStore", "");
System.setProperty("javax.net.ssl.keyStorePassword", "");
System.setProperty("javax.net.ssl.trustStore", "");
System.setProperty("javax.net.ssl.trustStorePassword", "");
// Enable OCSP Stapling on both client and server sides, but turn off
// client-side OCSP for revocation checking. This ensures that the
// revocation information from the test has to come via stapling.
System.setProperty("jdk.tls.client.enableStatusRequestExtension",
Boolean.toString(true));
System.setProperty("jdk.tls.server.enableStatusRequestExtension",
Boolean.toString(true));
Security.setProperty("ocsp.enable", "false");
}
/*
* Run the test.
*
* Sit in a tight loop, both engines calling wrap/unwrap regardless
* of whether data is available or not. We do this until both engines
* report back they are closed.
*
* The main loop handles all of the I/O phases of the SSLEngine's
* lifetime:
*
* initial handshaking
* application data transfer
* engine closing
*
* One could easily separate these phases into separate
* sections of code.
*/
private void runTest() throws Exception {
boolean dataDone = false;
createSSLEngines();
createBuffers();
SSLEngineResult clientResult; // results from client's last operation
SSLEngineResult serverResult; // results from server's last operation
/*
* Examining the SSLEngineResults could be much more involved,
* and may alter the overall flow of the application.
*
* For example, if we received a BUFFER_OVERFLOW when trying
* to write to the output pipe, we could reallocate a larger
* pipe, but instead we wait for the peer to drain it.
*/
while (!isEngineClosed(clientEngine) ||
!isEngineClosed(serverEngine)) {
log("================");
clientResult = clientEngine.wrap(clientOut, cTOs);
log("client wrap: ", clientResult);
runDelegatedTasks(clientResult, clientEngine);
serverResult = serverEngine.wrap(serverOut, sTOc);
log("server wrap: ", serverResult);
runDelegatedTasks(serverResult, serverEngine);
cTOs.flip();
sTOc.flip();
log("----");
clientResult = clientEngine.unwrap(sTOc, clientIn);
log("client unwrap: ", clientResult);
runDelegatedTasks(clientResult, clientEngine);
serverResult = serverEngine.unwrap(cTOs, serverIn);
log("server unwrap: ", serverResult);
runDelegatedTasks(serverResult, serverEngine);
cTOs.compact();
sTOc.compact();
/*
* After we've transfered all application data between the client
* and server, we close the clientEngine's outbound stream.
* This generates a close_notify handshake message, which the
* server engine receives and responds by closing itself.
*/
if (!dataDone && (clientOut.limit() == serverIn.position()) &&
(serverOut.limit() == clientIn.position())) {
/*
* A sanity check to ensure we got what was sent.
*/
checkTransfer(serverOut, clientIn);
checkTransfer(clientOut, serverIn);
log("\tClosing clientEngine's *OUTBOUND*...");
clientEngine.closeOutbound();
dataDone = true;
}
}
}
/*
* Using the SSLContext created during object creation,
* create/configure the SSLEngines we'll use for this test.
*/
private void createSSLEngines() throws Exception {
// Initialize the KeyManager and TrustManager for the server
KeyManagerFactory servKmf = KeyManagerFactory.getInstance("PKIX");
servKmf.init(serverKeystore, passwd.toCharArray());
TrustManagerFactory servTmf =
TrustManagerFactory.getInstance("PKIX");
servTmf.init(trustStore);
// Initialize the TrustManager for the client with revocation checking
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustStore,
new X509CertSelector());
pkixParams.setRevocationEnabled(true);
ManagerFactoryParameters mfp =
new CertPathTrustManagerParameters(pkixParams);
TrustManagerFactory cliTmf =
TrustManagerFactory.getInstance("PKIX");
cliTmf.init(mfp);
// Create the SSLContexts from the factories
SSLContext servCtx = SSLContext.getInstance("TLS");
servCtx.init(servKmf.getKeyManagers(), servTmf.getTrustManagers(),
null);
SSLContext cliCtx = SSLContext.getInstance("TLS");
cliCtx.init(null, cliTmf.getTrustManagers(), null);
/*
* Configure the serverEngine to act as a server in the SSL/TLS
* handshake.
*/
serverEngine = servCtx.createSSLEngine();
serverEngine.setUseClientMode(false);
serverEngine.setNeedClientAuth(false);
/*
* Similar to above, but using client mode instead.
*/
clientEngine = cliCtx.createSSLEngine("client", 80);
clientEngine.setUseClientMode(true);
}
/*
* Create and size the buffers appropriately.
*/
private void createBuffers() {
/*
* We'll assume the buffer sizes are the same
* between client and server.
*/
SSLSession session = clientEngine.getSession();
int appBufferMax = session.getApplicationBufferSize();
int netBufferMax = session.getPacketBufferSize();
/*
* We'll make the input buffers a bit bigger than the max needed
* size, so that unwrap()s following a successful data transfer
* won't generate BUFFER_OVERFLOWS.
*
* We'll use a mix of direct and indirect ByteBuffers for
* tutorial purposes only. In reality, only use direct
* ByteBuffers when they give a clear performance enhancement.
*/
clientIn = ByteBuffer.allocate(appBufferMax + 50);
serverIn = ByteBuffer.allocate(appBufferMax + 50);
cTOs = ByteBuffer.allocateDirect(netBufferMax);
sTOc = ByteBuffer.allocateDirect(netBufferMax);
clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes());
}
/*
* If the result indicates that we have outstanding tasks to do,
* go ahead and run them in this thread.
*/
private static void runDelegatedTasks(SSLEngineResult result,
SSLEngine engine) throws Exception {
if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
Runnable runnable;
while ((runnable = engine.getDelegatedTask()) != null) {
log("\trunning delegated task...");
runnable.run();
}
HandshakeStatus hsStatus = engine.getHandshakeStatus();
if (hsStatus == HandshakeStatus.NEED_TASK) {
throw new Exception(
"handshake shouldn't need additional tasks");
}
log("\tnew HandshakeStatus: " + hsStatus);
}
}
private static boolean isEngineClosed(SSLEngine engine) {
return (engine.isOutboundDone() && engine.isInboundDone());
}
/*
* Simple check to make sure everything came across as expected.
*/
private static void checkTransfer(ByteBuffer a, ByteBuffer b)
throws Exception {
a.flip();
b.flip();
if (!a.equals(b)) {
throw new Exception("Data didn't transfer cleanly");
} else {
log("\tData transferred cleanly");
}
a.position(a.limit());
b.position(b.limit());
a.limit(a.capacity());
b.limit(b.capacity());
}
/*
* Logging code
*/
private static boolean resultOnce = true;
private static void log(String str, SSLEngineResult result) {
if (!logging) {
return;
}
if (resultOnce) {
resultOnce = false;
System.out.println("The format of the SSLEngineResult is: \n" +
"\t\"getStatus() / getHandshakeStatus()\" +\n" +
"\t\"bytesConsumed() / bytesProduced()\"\n");
}
HandshakeStatus hsStatus = result.getHandshakeStatus();
log(str +
result.getStatus() + "/" + hsStatus + ", " +
result.bytesConsumed() + "/" + result.bytesProduced() +
" bytes");
if (hsStatus == HandshakeStatus.FINISHED) {
log("\t...ready for application data");
}
}
private static void log(String str) {
if (logging) {
System.out.println(str);
}
}
/**
* Creates the PKI components necessary for this test, including
* Root CA, Intermediate CA and SSL server certificates, the keystores
* for each entity, a client trust store, and starts the OCSP responders.
*/
private static void createPKI() throws Exception {
CertificateBuilder cbld = new CertificateBuilder();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyStore.Builder keyStoreBuilder =
KeyStore.Builder.newInstance("PKCS12", null,
new KeyStore.PasswordProtection(passwd.toCharArray()));
// Generate Root, IntCA, EE keys
KeyPair rootCaKP = keyGen.genKeyPair();
log("Generated Root CA KeyPair");
KeyPair intCaKP = keyGen.genKeyPair();
log("Generated Intermediate CA KeyPair");
KeyPair sslKP = keyGen.genKeyPair();
log("Generated SSL Cert KeyPair");
// Set up the Root CA Cert
cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany");
cbld.setPublicKey(rootCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("1"));
// Make a 3 year validity starting from 60 days ago
long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
long end = start + TimeUnit.DAYS.toMillis(1085);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
// Make our Root CA Cert!
X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Root CA Created:\n" + certInfo(rootCert));
// Now build a keystore and add the keys and cert
rootKeystore = keyStoreBuilder.getKeyStore();
java.security.cert.Certificate[] rootChain = {rootCert};
rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(),
passwd.toCharArray(), rootChain);
// Now fire up the OCSP responder
rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null);
rootOcsp.enableLog(logging);
rootOcsp.setNextUpdateInterval(3600);
rootOcsp.start();
Thread.sleep(1000); // Give the server a second to start up
rootOcspPort = rootOcsp.getPort();
String rootRespURI = "http://localhost:" + rootOcspPort;
log("Root OCSP Responder URI is " + rootRespURI);
// Now that we have the root keystore and OCSP responder we can
// create our intermediate CA.
cbld.reset();
cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany");
cbld.setPublicKey(intCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("100"));
// Make a 2 year validity starting from 30 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30);
end = start + TimeUnit.DAYS.toMillis(730);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
cbld.addAIAExt(Collections.singletonList(rootRespURI));
// Make our Intermediate CA Cert!
X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Intermediate CA Created:\n" + certInfo(intCaCert));
// Provide intermediate CA cert revocation info to the Root CA
// OCSP responder.
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
revInfo.put(intCaCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
rootOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
intKeystore = keyStoreBuilder.getKeyStore();
java.security.cert.Certificate[] intChain = {intCaCert, rootCert};
intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(),
passwd.toCharArray(), intChain);
intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// Now fire up the Intermediate CA OCSP responder
intOcsp = new SimpleOCSPServer(intKeystore, passwd,
INT_ALIAS, null);
intOcsp.enableLog(logging);
intOcsp.setNextUpdateInterval(3600);
intOcsp.start();
Thread.sleep(1000);
intOcspPort = intOcsp.getPort();
String intCaRespURI = "http://localhost:" + intOcspPort;
log("Intermediate CA OCSP Responder URI is " + intCaRespURI);
// Last but not least, let's make our SSLCert and add it to its own
// Keystore
cbld.reset();
cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany");
cbld.setPublicKey(sslKP.getPublic());
cbld.setSerialNumber(new BigInteger("4096"));
// Make a 1 year validity starting from 7 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
end = start + TimeUnit.DAYS.toMillis(365);
cbld.setValidity(new Date(start), new Date(end));
// Add extensions
addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic());
boolean[] kuBits = {true, false, true, false, false, false,
false, false, false};
cbld.addKeyUsageExt(kuBits);
List<String> ekuOids = new ArrayList<>();
ekuOids.add("1.3.6.1.5.5.7.3.1");
ekuOids.add("1.3.6.1.5.5.7.3.2");
cbld.addExtendedKeyUsageExt(ekuOids);
cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost"));
cbld.addAIAExt(Collections.singletonList(intCaRespURI));
// Make our SSL Server Cert!
X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(),
"SHA256withRSA");
log("SSL Certificate Created:\n" + certInfo(sslCert));
// Provide SSL server cert revocation info to the Intermeidate CA
// OCSP responder.
revInfo = new HashMap<>();
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
intOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
serverKeystore = keyStoreBuilder.getKeyStore();
java.security.cert.Certificate[] sslChain = {sslCert, intCaCert, rootCert};
serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(),
passwd.toCharArray(), sslChain);
serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// And finally a Trust Store for the client
trustStore = keyStoreBuilder.getKeyStore();
trustStore.setCertificateEntry(ROOT_ALIAS, rootCert);
}
private static void addCommonExts(CertificateBuilder cbld,
PublicKey subjKey, PublicKey authKey) throws IOException {
cbld.addSubjectKeyIdExt(subjKey);
cbld.addAuthorityKeyIdExt(authKey);
}
private static void addCommonCAExts(CertificateBuilder cbld)
throws IOException {
cbld.addBasicConstraintsExt(true, true, -1);
// Set key usage bits for digitalSignature, keyCertSign and cRLSign
boolean[] kuBitSettings = {true, false, false, false, false, true,
true, false, false};
cbld.addKeyUsageExt(kuBitSettings);
}
/**
* Helper routine that dumps only a few cert fields rather than
* the whole toString() output.
*
* @param cert an X509Certificate to be displayed
*
* @return the String output of the issuer, subject and
* serial number
*/
private static String certInfo(X509Certificate cert) {
StringBuilder sb = new StringBuilder();
sb.append("Issuer: ").append(cert.getIssuerX500Principal()).
append("\n");
sb.append("Subject: ").append(cert.getSubjectX500Principal()).
append("\n");
sb.append("Serial: ").append(cert.getSerialNumber()).append("\n");
return sb.toString();
}
/**
* Checks a validation failure to see if it failed for the reason we think
* it should. This comes in as an SSLException of some sort, but it
* encapsulates a ValidatorException which in turn encapsulates the
* CertPathValidatorException we are interested in.
*
* @param e the exception thrown at the top level
* @param reason the underlying CertPathValidatorException BasicReason
* we are expecting it to have.
*
* @return true if the reason matches up, false otherwise.
*/
static boolean checkClientValidationFailure(Exception e,
CertPathValidatorException.BasicReason reason) {
boolean result = false;
if (e instanceof SSLException) {
Throwable sslhe = e.getCause();
if (sslhe instanceof SSLHandshakeException) {
Throwable valExc = sslhe.getCause();
if (valExc instanceof sun.security.validator.ValidatorException) {
Throwable cause = valExc.getCause();
if (cause instanceof CertPathValidatorException) {
CertPathValidatorException cpve =
(CertPathValidatorException)cause;
if (cpve.getReason() == reason) {
result = true;
}
}
}
}
}
return result;
}
}

View file

@ -0,0 +1,905 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// SunJSSE does not support dynamic system properties, no way to re-use
// system properties in samevm/agentvm mode.
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS
* @library ../../../../java/security/testlibrary
* @build CertificateBuilder SimpleOCSPServer
* @run main/othervm SSLSocketWithStapling
*/
import java.io.*;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import javax.net.ssl.*;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.Certificate;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.PKIXRevocationChecker.Option;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import sun.security.testlibrary.SimpleOCSPServer;
import sun.security.testlibrary.CertificateBuilder;
public class SSLSocketWithStapling {
/*
* =============================================================
* Set the various variables needed for the tests, then
* specify what tests to run on each side.
*/
// Turn on TLS debugging
static boolean debug = false;
/*
* Should we run the client or server in a separate thread?
* Both sides can throw exceptions, but do you have a preference
* as to which side should be the main thread.
*/
static boolean separateServerThread = true;
Thread clientThread = null;
Thread serverThread = null;
static String passwd = "passphrase";
static String ROOT_ALIAS = "root";
static String INT_ALIAS = "intermediate";
static String SSL_ALIAS = "ssl";
/*
* Is the server ready to serve?
*/
volatile static boolean serverReady = false;
volatile int serverPort = 0;
volatile Exception serverException = null;
volatile Exception clientException = null;
// PKI components we will need for this test
static KeyStore rootKeystore; // Root CA Keystore
static KeyStore intKeystore; // Intermediate CA Keystore
static KeyStore serverKeystore; // SSL Server Keystore
static KeyStore trustStore; // SSL Client trust store
static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder
static int rootOcspPort; // Port number for root OCSP
static SimpleOCSPServer intOcsp; // Intermediate CA OCSP Responder
static int intOcspPort; // Port number for intermed. OCSP
/*
* If the client or server is doing some kind of object creation
* that the other side depends on, and that thread prematurely
* exits, you may experience a hang. The test harness will
* terminate all hung threads after its timeout has expired,
* currently 3 minutes by default, but you might try to be
* smart about it....
*/
public static void main(String[] args) throws Exception {
if (debug) {
System.setProperty("javax.net.debug", "ssl");
}
// Create the PKI we will use for the test and start the OCSP servers
createPKI();
testAllDefault();
testPKIXParametersRevEnabled();
testRevokedCertificate();
testHardFailFallback();
testSoftFailFallback();
testLatencyNoStaple(false);
testLatencyNoStaple(true);
// shut down the OCSP responders before finishing the test
intOcsp.stop();
rootOcsp.stop();
}
/**
* Default test using no externally-configured PKIXBuilderParameters
*/
static void testAllDefault() throws Exception {
ClientParameters cliParams = new ClientParameters();
ServerParameters servParams = new ServerParameters();
serverReady = false;
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
// We will prove revocation checking is disabled by marking the SSL
// certificate as revoked. The test would only pass if revocation
// checking did not happen.
X509Certificate sslCert =
(X509Certificate)serverKeystore.getCertificate(SSL_ALIAS);
Date fiveMinsAgo = new Date(System.currentTimeMillis() -
TimeUnit.MINUTES.toMillis(5));
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_REVOKED,
fiveMinsAgo));
intOcsp.updateStatusDb(revInfo);
System.out.println("=======================================");
System.out.println("Stapling enabled, default configuration");
System.out.println("=======================================");
SSLSocketWithStapling sslTest = new SSLSocketWithStapling(cliParams,
servParams);
TestResult tr = sslTest.getResult();
if (tr.clientExc != null) {
throw tr.clientExc;
} else if (tr.serverExc != null) {
throw tr.serverExc;
}
// Return the ssl certificate to non-revoked status
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
intOcsp.updateStatusDb(revInfo);
System.out.println(" PASS");
System.out.println("=======================================\n");
}
/**
* Do a basic connection using PKIXParameters with revocation checking
* enabled and client-side OCSP disabled. It will only pass if all
* stapled responses are present, valid and have a GOOD status.
*/
static void testPKIXParametersRevEnabled() throws Exception {
ClientParameters cliParams = new ClientParameters();
ServerParameters servParams = new ServerParameters();
serverReady = false;
System.out.println("=====================================");
System.out.println("Stapling enabled, PKIXParameters with");
System.out.println("Revocation checking enabled ");
System.out.println("=====================================");
cliParams.pkixParams = new PKIXBuilderParameters(trustStore,
new X509CertSelector());
cliParams.pkixParams.setRevocationEnabled(true);
Security.setProperty("ocsp.enable", "false");
SSLSocketWithStapling sslTest = new SSLSocketWithStapling(cliParams,
servParams);
TestResult tr = sslTest.getResult();
if (tr.clientExc != null) {
throw tr.clientExc;
} else if (tr.serverExc != null) {
throw tr.serverExc;
}
System.out.println(" PASS");
System.out.println("=====================================\n");
}
/**
* Perform a test where the certificate is revoked and placed in the
* TLS handshake. Client-side OCSP is disabled, so this test will only
* pass if the OCSP response is found, since we will check the
* CertPathValidatorException reason for revoked status.
*/
static void testRevokedCertificate() throws Exception {
ClientParameters cliParams = new ClientParameters();
ServerParameters servParams = new ServerParameters();
serverReady = false;
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
// We will prove revocation checking is disabled by marking the SSL
// certificate as revoked. The test would only pass if revocation
// checking did not happen.
X509Certificate sslCert =
(X509Certificate)serverKeystore.getCertificate(SSL_ALIAS);
Date fiveMinsAgo = new Date(System.currentTimeMillis() -
TimeUnit.MINUTES.toMillis(5));
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_REVOKED,
fiveMinsAgo));
intOcsp.updateStatusDb(revInfo);
System.out.println("=======================================");
System.out.println("Stapling enabled, default configuration");
System.out.println("=======================================");
cliParams.pkixParams = new PKIXBuilderParameters(trustStore,
new X509CertSelector());
cliParams.pkixParams.setRevocationEnabled(true);
Security.setProperty("ocsp.enable", "false");
SSLSocketWithStapling sslTest = new SSLSocketWithStapling(cliParams,
servParams);
TestResult tr = sslTest.getResult();
if (!checkClientValidationFailure(tr.clientExc, BasicReason.REVOKED)) {
if (tr.clientExc != null) {
throw tr.clientExc;
} else {
throw new RuntimeException(
"Expected client failure, but the client succeeded");
}
}
// Return the ssl certificate to non-revoked status
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
intOcsp.updateStatusDb(revInfo);
System.out.println(" PASS");
System.out.println("=======================================\n");
}
/**
* Test a case where client-side stapling is attempted, but does not
* occur because OCSP responders are unreachable. This should use a
* default hard-fail behavior.
*/
static void testHardFailFallback() throws Exception {
ClientParameters cliParams = new ClientParameters();
ServerParameters servParams = new ServerParameters();
serverReady = false;
// Stop the OCSP responders and give a 1 second delay before
// running the test.
intOcsp.stop();
rootOcsp.stop();
Thread.sleep(1000);
System.out.println("=======================================");
System.out.println("Stapling enbled in client and server,");
System.out.println("but OCSP responders disabled.");
System.out.println("PKIXParameters with Revocation checking");
System.out.println("enabled.");
System.out.println("=======================================");
Security.setProperty("ocsp.enable", "true");
cliParams.pkixParams = new PKIXBuilderParameters(trustStore,
new X509CertSelector());
cliParams.pkixParams.setRevocationEnabled(true);
SSLSocketWithStapling sslTest = new SSLSocketWithStapling(cliParams,
servParams);
TestResult tr = sslTest.getResult();
if (!checkClientValidationFailure(tr.clientExc,
BasicReason.UNDETERMINED_REVOCATION_STATUS)) {
if (tr.clientExc != null) {
throw tr.clientExc;
} else {
throw new RuntimeException(
"Expected client failure, but the client succeeded");
}
}
System.out.println(" PASS");
System.out.println("=======================================\n");
// Start the OCSP responders up again
intOcsp.start();
rootOcsp.start();
}
/**
* Test a case where client-side stapling is attempted, but does not
* occur because OCSP responders are unreachable. Client-side OCSP
* checking is enabled for this, with SOFT_FAIL.
*/
static void testSoftFailFallback() throws Exception {
ClientParameters cliParams = new ClientParameters();
ServerParameters servParams = new ServerParameters();
serverReady = false;
// Stop the OCSP responders and give a 1 second delay before
// running the test.
intOcsp.stop();
rootOcsp.stop();
Thread.sleep(1000);
System.out.println("=======================================");
System.out.println("Stapling enbled in client and server,");
System.out.println("but OCSP responders disabled.");
System.out.println("PKIXParameters with Revocation checking");
System.out.println("enabled and SOFT_FAIL.");
System.out.println("=======================================");
Security.setProperty("ocsp.enable", "true");
cliParams.pkixParams = new PKIXBuilderParameters(trustStore,
new X509CertSelector());
cliParams.pkixParams.setRevocationEnabled(true);
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
cliParams.revChecker =
(PKIXRevocationChecker)cpv.getRevocationChecker();
cliParams.revChecker.setOptions(EnumSet.of(Option.SOFT_FAIL));
SSLSocketWithStapling sslTest = new SSLSocketWithStapling(cliParams,
servParams);
TestResult tr = sslTest.getResult();
if (tr.clientExc != null) {
throw tr.clientExc;
} else if (tr.serverExc != null) {
throw tr.serverExc;
}
System.out.println(" PASS");
System.out.println("=======================================\n");
// Start the OCSP responders up again
intOcsp.start();
rootOcsp.start();
}
/**
* This test initiates stapling from the client, but the server does not
* support OCSP stapling for this connection. In this case it happens
* because the latency of the OCSP responders is longer than the server
* is willing to wait. To keep the test streamlined, we will set the server
* latency to a 1 second wait, and set the responder latency to 3 seconds.
*
* @param fallback if we allow client-side OCSP fallback, which
* will change the result from the client failing with CPVE (no fallback)
* to a pass (fallback active).
*/
static void testLatencyNoStaple(Boolean fallback) throws Exception {
ClientParameters cliParams = new ClientParameters();
ServerParameters servParams = new ServerParameters();
serverReady = false;
// Stop the OCSP responders and give a 1 second delay before
// running the test.
intOcsp.stop();
rootOcsp.stop();
Thread.sleep(1000);
intOcsp.setDelay(3000);
rootOcsp.setDelay(3000);
rootOcsp.start();
intOcsp.start();
Thread.sleep(1000);
System.out.println("========================================");
System.out.println("Stapling enbled in client. Server does");
System.out.println("not support stapling due to OCSP latency.");
System.out.println("PKIXParameters with Revocation checking");
System.out.println("enabled, client-side OCSP checking is.");
System.out.println(fallback ? "enabled" : "disabled");
System.out.println("========================================");
Security.setProperty("ocsp.enable", fallback.toString());
cliParams.pkixParams = new PKIXBuilderParameters(trustStore,
new X509CertSelector());
cliParams.pkixParams.setRevocationEnabled(true);
servParams.respTimeout = 1000;
SSLSocketWithStapling sslTest = new SSLSocketWithStapling(cliParams,
servParams);
TestResult tr = sslTest.getResult();
if (fallback) {
if (tr.clientExc != null) {
throw tr.clientExc;
} else if (tr.serverExc != null) {
throw tr.serverExc;
}
} else {
if (!checkClientValidationFailure(tr.clientExc,
BasicReason.UNDETERMINED_REVOCATION_STATUS)) {
if (tr.clientExc != null) {
throw tr.clientExc;
} else {
throw new RuntimeException(
"Expected client failure, but the client succeeded");
}
}
}
System.out.println(" PASS");
System.out.println("========================================\n");
// Remove the OCSP responder latency
intOcsp.stop();
rootOcsp.stop();
Thread.sleep(1000);
intOcsp.setDelay(0);
rootOcsp.setDelay(0);
rootOcsp.start();
intOcsp.start();
}
/*
* Define the server side of the test.
*
* If the server prematurely exits, serverReady will be set to true
* to avoid infinite hangs.
*/
void doServerSide(ServerParameters servParams) throws Exception {
// Selectively enable or disable the feature
System.setProperty("jdk.tls.server.enableStatusRequestExtension",
Boolean.toString(servParams.enabled));
// Set all the other operating parameters
System.setProperty("jdk.tls.stapling.cacheSize",
Integer.toString(servParams.cacheSize));
System.setProperty("jdk.tls.stapling.cacheLifetime",
Integer.toString(servParams.cacheLifetime));
System.setProperty("jdk.tls.stapling.responseTimeout",
Integer.toString(servParams.respTimeout));
System.setProperty("jdk.tls.stapling.responderURI", servParams.respUri);
System.setProperty("jdk.tls.stapling.responderOverride",
Boolean.toString(servParams.respOverride));
System.setProperty("jdk.tls.stapling.ignoreExtensions",
Boolean.toString(servParams.ignoreExts));
// Set keystores and trust stores for the server
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(serverKeystore, passwd.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslc = SSLContext.getInstance("TLS");
sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory sslssf = sslc.getServerSocketFactory();
SSLServerSocket sslServerSocket =
(SSLServerSocket) sslssf.createServerSocket(serverPort);
serverPort = sslServerSocket.getLocalPort();
/*
* Signal Client, we're ready for his connect.
*/
serverReady = true;
try (SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
InputStream sslIS = sslSocket.getInputStream();
OutputStream sslOS = sslSocket.getOutputStream()) {
int numberIn = sslIS.read();
int numberSent = 85;
log("Server received number: " + numberIn);
sslOS.write(numberSent);
sslOS.flush();
log("Server sent number: " + numberSent);
}
}
/*
* Define the client side of the test.
*
* If the server prematurely exits, serverReady will be set to true
* to avoid infinite hangs.
*/
void doClientSide(ClientParameters cliParams) throws Exception {
/*
* Wait for server to get started.
*/
while (!serverReady) {
Thread.sleep(50);
}
// Selectively enable or disable the feature
System.setProperty("jdk.tls.client.enableStatusRequestExtension",
Boolean.toString(cliParams.enabled));
// Create the Trust Manager Factory using the PKIX variant
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
// If we have a customized pkixParameters then use it
if (cliParams.pkixParams != null) {
// LIf we have a customized PKIXRevocationChecker, add
// it to the PKIXBuilderParameters.
if (cliParams.revChecker != null) {
cliParams.pkixParams.addCertPathChecker(cliParams.revChecker);
}
ManagerFactoryParameters trustParams =
new CertPathTrustManagerParameters(cliParams.pkixParams);
tmf.init(trustParams);
} else {
tmf.init(trustStore);
}
SSLContext sslc = SSLContext.getInstance("TLS");
sslc.init(null, tmf.getTrustManagers(), null);
SSLSocketFactory sslsf = sslc.getSocketFactory();
try (SSLSocket sslSocket = (SSLSocket)sslsf.createSocket("localhost",
serverPort);
InputStream sslIS = sslSocket.getInputStream();
OutputStream sslOS = sslSocket.getOutputStream()) {
int numberSent = 80;
sslOS.write(numberSent);
sslOS.flush();
log("Client sent number: " + numberSent);
int numberIn = sslIS.read();
log("Client received number:" + numberIn);
}
}
/*
* Primary constructor, used to drive remainder of the test.
*
* Fork off the other side, then do your work.
*/
SSLSocketWithStapling(ClientParameters cliParams,
ServerParameters servParams) throws Exception {
Exception startException = null;
try {
if (separateServerThread) {
startServer(servParams, true);
startClient(cliParams, false);
} else {
startClient(cliParams, true);
startServer(servParams, false);
}
} catch (Exception e) {
startException = e;
}
/*
* Wait for other side to close down.
*/
if (separateServerThread) {
if (serverThread != null) {
serverThread.join();
}
} else {
if (clientThread != null) {
clientThread.join();
}
}
}
/**
* Checks a validation failure to see if it failed for the reason we think
* it should. This comes in as an SSLException of some sort, but it
* encapsulates a ValidatorException which in turn encapsulates the
* CertPathValidatorException we are interested in.
*
* @param e the exception thrown at the top level
* @param reason the underlying CertPathValidatorException BasicReason
* we are expecting it to have.
*
* @return true if the reason matches up, false otherwise.
*/
static boolean checkClientValidationFailure(Exception e,
BasicReason reason) {
boolean result = false;
if (e instanceof SSLException) {
Throwable valExc = e.getCause();
if (valExc instanceof sun.security.validator.ValidatorException) {
Throwable cause = valExc.getCause();
if (cause instanceof CertPathValidatorException) {
CertPathValidatorException cpve =
(CertPathValidatorException)cause;
if (cpve.getReason() == reason) {
result = true;
}
}
}
}
return result;
}
TestResult getResult() {
TestResult tr = new TestResult();
tr.clientExc = clientException;
tr.serverExc = serverException;
return tr;
}
void startServer(ServerParameters servParams, boolean newThread)
throws Exception {
if (newThread) {
serverThread = new Thread() {
public void run() {
try {
doServerSide(servParams);
} catch (Exception e) {
/*
* Our server thread just died.
*
* Release the client, if not active already...
*/
System.err.println("Server died...");
serverReady = true;
serverException = e;
}
}
};
serverThread.start();
} else {
try {
doServerSide(servParams);
} catch (Exception e) {
serverException = e;
} finally {
serverReady = true;
}
}
}
void startClient(ClientParameters cliParams, boolean newThread)
throws Exception {
if (newThread) {
clientThread = new Thread() {
public void run() {
try {
doClientSide(cliParams);
} catch (Exception e) {
/*
* Our client thread just died.
*/
System.err.println("Client died...");
clientException = e;
}
}
};
clientThread.start();
} else {
try {
doClientSide(cliParams);
} catch (Exception e) {
clientException = e;
}
}
}
/**
* Creates the PKI components necessary for this test, including
* Root CA, Intermediate CA and SSL server certificates, the keystores
* for each entity, a client trust store, and starts the OCSP responders.
*/
private static void createPKI() throws Exception {
CertificateBuilder cbld = new CertificateBuilder();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyStore.Builder keyStoreBuilder =
KeyStore.Builder.newInstance("PKCS12", null,
new KeyStore.PasswordProtection(passwd.toCharArray()));
// Generate Root, IntCA, EE keys
KeyPair rootCaKP = keyGen.genKeyPair();
log("Generated Root CA KeyPair");
KeyPair intCaKP = keyGen.genKeyPair();
log("Generated Intermediate CA KeyPair");
KeyPair sslKP = keyGen.genKeyPair();
log("Generated SSL Cert KeyPair");
// Set up the Root CA Cert
cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany");
cbld.setPublicKey(rootCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("1"));
// Make a 3 year validity starting from 60 days ago
long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
long end = start + TimeUnit.DAYS.toMillis(1085);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
// Make our Root CA Cert!
X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Root CA Created:\n" + certInfo(rootCert));
// Now build a keystore and add the keys and cert
rootKeystore = keyStoreBuilder.getKeyStore();
Certificate[] rootChain = {rootCert};
rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(),
passwd.toCharArray(), rootChain);
// Now fire up the OCSP responder
rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null);
rootOcsp.enableLog(debug);
rootOcsp.setNextUpdateInterval(3600);
rootOcsp.start();
Thread.sleep(1000); // Give the server a second to start up
rootOcspPort = rootOcsp.getPort();
String rootRespURI = "http://localhost:" + rootOcspPort;
log("Root OCSP Responder URI is " + rootRespURI);
// Now that we have the root keystore and OCSP responder we can
// create our intermediate CA.
cbld.reset();
cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany");
cbld.setPublicKey(intCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("100"));
// Make a 2 year validity starting from 30 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30);
end = start + TimeUnit.DAYS.toMillis(730);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
cbld.addAIAExt(Collections.singletonList(rootRespURI));
// Make our Intermediate CA Cert!
X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Intermediate CA Created:\n" + certInfo(intCaCert));
// Provide intermediate CA cert revocation info to the Root CA
// OCSP responder.
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
revInfo.put(intCaCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
rootOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
intKeystore = keyStoreBuilder.getKeyStore();
Certificate[] intChain = {intCaCert, rootCert};
intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(),
passwd.toCharArray(), intChain);
intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// Now fire up the Intermediate CA OCSP responder
intOcsp = new SimpleOCSPServer(intKeystore, passwd,
INT_ALIAS, null);
intOcsp.enableLog(debug);
intOcsp.setNextUpdateInterval(3600);
intOcsp.start();
Thread.sleep(1000);
intOcspPort = intOcsp.getPort();
String intCaRespURI = "http://localhost:" + intOcspPort;
log("Intermediate CA OCSP Responder URI is " + intCaRespURI);
// Last but not least, let's make our SSLCert and add it to its own
// Keystore
cbld.reset();
cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany");
cbld.setPublicKey(sslKP.getPublic());
cbld.setSerialNumber(new BigInteger("4096"));
// Make a 1 year validity starting from 7 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
end = start + TimeUnit.DAYS.toMillis(365);
cbld.setValidity(new Date(start), new Date(end));
// Add extensions
addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic());
boolean[] kuBits = {true, false, true, false, false, false,
false, false, false};
cbld.addKeyUsageExt(kuBits);
List<String> ekuOids = new ArrayList<>();
ekuOids.add("1.3.6.1.5.5.7.3.1");
ekuOids.add("1.3.6.1.5.5.7.3.2");
cbld.addExtendedKeyUsageExt(ekuOids);
cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost"));
cbld.addAIAExt(Collections.singletonList(intCaRespURI));
// Make our SSL Server Cert!
X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(),
"SHA256withRSA");
log("SSL Certificate Created:\n" + certInfo(sslCert));
// Provide SSL server cert revocation info to the Intermeidate CA
// OCSP responder.
revInfo = new HashMap<>();
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
intOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
serverKeystore = keyStoreBuilder.getKeyStore();
Certificate[] sslChain = {sslCert, intCaCert, rootCert};
serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(),
passwd.toCharArray(), sslChain);
serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// And finally a Trust Store for the client
trustStore = keyStoreBuilder.getKeyStore();
trustStore.setCertificateEntry(ROOT_ALIAS, rootCert);
}
private static void addCommonExts(CertificateBuilder cbld,
PublicKey subjKey, PublicKey authKey) throws IOException {
cbld.addSubjectKeyIdExt(subjKey);
cbld.addAuthorityKeyIdExt(authKey);
}
private static void addCommonCAExts(CertificateBuilder cbld)
throws IOException {
cbld.addBasicConstraintsExt(true, true, -1);
// Set key usage bits for digitalSignature, keyCertSign and cRLSign
boolean[] kuBitSettings = {true, false, false, false, false, true,
true, false, false};
cbld.addKeyUsageExt(kuBitSettings);
}
/**
* Helper routine that dumps only a few cert fields rather than
* the whole toString() output.
*
* @param cert an X509Certificate to be displayed
*
* @return the String output of the issuer, subject and
* serial number
*/
private static String certInfo(X509Certificate cert) {
StringBuilder sb = new StringBuilder();
sb.append("Issuer: ").append(cert.getIssuerX500Principal()).
append("\n");
sb.append("Subject: ").append(cert.getSubjectX500Principal()).
append("\n");
sb.append("Serial: ").append(cert.getSerialNumber()).append("\n");
return sb.toString();
}
/**
* Log a message on stdout
*
* @param message The message to log
*/
private static void log(String message) {
if (debug) {
System.out.println(message);
}
}
// The following two classes are Simple nested class to group a handful
// of configuration parameters used before starting a client or server.
// We'll just access the data members directly for convenience.
static class ClientParameters {
boolean enabled = true;
PKIXBuilderParameters pkixParams = null;
PKIXRevocationChecker revChecker = null;
ClientParameters() { }
}
static class ServerParameters {
boolean enabled = true;
int cacheSize = 256;
int cacheLifetime = 3600;
int respTimeout = 5000;
String respUri = "";
boolean respOverride = false;
boolean ignoreExts = false;
ServerParameters() { }
}
static class TestResult {
Exception serverExc = null;
Exception clientExc = null;
}
}

View file

@ -0,0 +1,458 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8046321
* @summary Unit tests for OCSPNonceExtension objects
*/
import java.security.cert.Extension;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import sun.security.util.DerValue;
import sun.security.util.DerInputStream;
import sun.security.util.ObjectIdentifier;
import sun.security.provider.certpath.OCSPNonceExtension;
import sun.security.x509.PKIXExtensions;
public class OCSPNonceExtensionTests {
public static final boolean DEBUG = true;
public static final String OCSP_NONCE_OID = "1.3.6.1.5.5.7.48.1.2";
public static final String ELEMENT_NONCE = "nonce";
public static final String EXT_NAME = "OCSPNonce";
// DER encoding for OCSP nonce extension:
// OID = 1.3.6.1.5.5.7.48.1.2
// Critical = true
// 48 bytes of 0xDEADBEEF
public static final byte[] OCSP_NONCE_DER = {
48, 66, 6, 9, 43, 6, 1, 5,
5, 7, 48, 1, 2, 1, 1, -1,
4, 50, 4, 48, -34, -83, -66, -17,
-34, -83, -66, -17, -34, -83, -66, -17,
-34, -83, -66, -17, -34, -83, -66, -17,
-34, -83, -66, -17, -34, -83, -66, -17,
-34, -83, -66, -17, -34, -83, -66, -17,
-34, -83, -66, -17, -34, -83, -66, -17,
-34, -83, -66, -17,
};
// 16 bytes of 0xDEADBEEF
public static final byte[] DEADBEEF_16 = {
-34, -83, -66, -17, -34, -83, -66, -17,
-34, -83, -66, -17, -34, -83, -66, -17,
};
// DER encoded extension using 16 bytes of DEADBEEF
public static final byte[] OCSP_NONCE_DB16 = {
48, 31, 6, 9, 43, 6, 1, 5,
5, 7, 48, 1, 2, 4, 18, 4,
16, -34, -83, -66, -17, -34, -83, -66,
-17, -34, -83, -66, -17, -34, -83, -66,
-17
};
public static void main(String [] args) throws Exception {
Map<String, TestCase> testList =
new LinkedHashMap<String, TestCase>() {{
put("CTOR Test (provide length)", testCtorByLength);
put("CTOR Test (provide extension DER encoding)",
testCtorSuperByDerValue);
put("Use set() call to provide random data", testResetValue);
put("Test get() method", testGet);
put("test set() method", testSet);
put("Test getElements() method", testGetElements);
put("Test getName() method", testGetName);
put("Test delete() method", testDelete);
}};
System.out.println("============ Tests ============");
int testNo = 0;
int numberFailed = 0;
Map.Entry<Boolean, String> result;
for (String testName : testList.keySet()) {
System.out.println("Test " + ++testNo + ": " + testName);
result = testList.get(testName).runTest();
System.out.print("Result: " + (result.getKey() ? "PASS" : "FAIL"));
System.out.println(" " +
(result.getValue() != null ? result.getValue() : ""));
System.out.println("-------------------------------------------");
if (!result.getKey()) {
numberFailed++;
}
}
System.out.println("End Results: " + (testList.size() - numberFailed) +
" Passed" + ", " + numberFailed + " Failed.");
if (numberFailed > 0) {
throw new RuntimeException(
"One or more tests failed, see test output for details");
}
}
private static void dumpHexBytes(byte[] data) {
if (data != null) {
for (int i = 0; i < data.length; i++) {
if (i % 16 == 0 && i != 0) {
System.out.print("\n");
}
System.out.print(String.format("%02X ", data[i]));
}
System.out.print("\n");
}
}
private static void debuglog(String message) {
if (DEBUG) {
System.out.println(message);
}
}
public static void verifyExtStructure(byte[] derData) throws IOException {
debuglog("verifyASN1Extension() received " + derData.length + " bytes");
DerInputStream dis = new DerInputStream(derData);
// The sequenceItems array should be either two or three elements
// long. If three, then the criticality bit setting has been asserted.
DerValue[] sequenceItems = dis.getSequence(3);
debuglog("Found sequence containing " + sequenceItems.length +
" elements");
if (sequenceItems.length != 2 && sequenceItems.length != 3) {
throw new RuntimeException("Incorrect number of items found in " +
"the SEQUENCE (Got " + sequenceItems.length +
", expected 2 or 3 items)");
}
int seqIndex = 0;
ObjectIdentifier extOid = sequenceItems[seqIndex++].getOID();
debuglog("Found OID: " + extOid.toString());
if (!extOid.equals((Object)PKIXExtensions.OCSPNonce_Id)) {
throw new RuntimeException("Incorrect OID (Got " +
extOid.toString() + ", expected " +
PKIXExtensions.OCSPNonce_Id.toString() + ")");
}
if (sequenceItems.length == 3) {
// Non-default criticality bit setting should be at index 1
boolean isCrit = sequenceItems[seqIndex++].getBoolean();
debuglog("Found BOOLEAN (critical): " + isCrit);
}
// The extnValue is an encapsulating OCTET STRING that contains the
// extension's value. For the OCSP Nonce, that value itself is also
// an OCTET STRING consisting of the random bytes.
DerValue extnValue =
new DerValue(sequenceItems[seqIndex++].getOctetString());
byte[] nonceData = extnValue.getOctetString();
debuglog("Found " + nonceData.length + " bytes of nonce data");
}
public interface TestCase {
Map.Entry<Boolean, String> runTest();
}
public static final TestCase testCtorByLength = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
Extension nonceByLen = new OCSPNonceExtension(32);
// Verify overall encoded extension structure
nonceByLen.encode(baos);
verifyExtStructure(baos.toByteArray());
// Verify the name, elements, and data conform to
// expected values for this specific object.
boolean crit = nonceByLen.isCritical();
String oid = nonceByLen.getId();
DerValue nonceData = new DerValue(nonceByLen.getValue());
if (crit) {
message = "Extension incorrectly marked critical";
} else if (!oid.equals(OCSP_NONCE_OID)) {
message = "Incorrect OID (Got " + oid + ", Expected " +
OCSP_NONCE_OID + ")";
} else if (nonceData.getTag() != DerValue.tag_OctetString) {
message = "Incorrect nonce data tag type (Got " +
String.format("0x%02X", nonceData.getTag()) +
", Expected 0x04)";
} else if (nonceData.getOctetString().length != 32) {
message = "Incorrect nonce byte length (Got " +
nonceData.getOctetString().length +
", Expected 32)";
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testCtorSuperByDerValue = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
Extension nonceByDer = new sun.security.x509.Extension(
new DerValue(OCSP_NONCE_DER));
// Verify overall encoded extension structure
nonceByDer.encode(baos);
verifyExtStructure(baos.toByteArray());
// Verify the name, elements, and data conform to
// expected values for this specific object.
boolean crit = nonceByDer.isCritical();
String oid = nonceByDer.getId();
DerValue nonceData = new DerValue(nonceByDer.getValue());
if (!crit) {
message = "Extension lacks expected criticality setting";
} else if (!oid.equals(OCSP_NONCE_OID)) {
message = "Incorrect OID (Got " + oid + ", Expected " +
OCSP_NONCE_OID + ")";
} else if (nonceData.getTag() != DerValue.tag_OctetString) {
message = "Incorrect nonce data tag type (Got " +
String.format("0x%02X", nonceData.getTag()) +
", Expected 0x04)";
} else if (nonceData.getOctetString().length != 48) {
message = "Incorrect nonce byte length (Got " +
nonceData.getOctetString().length +
", Expected 48)";
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testResetValue = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
OCSPNonceExtension nonce = new OCSPNonceExtension(32);
// Reset the nonce data to reflect 16 bytes of DEADBEEF
nonce.set(OCSPNonceExtension.NONCE, (Object)DEADBEEF_16);
// Verify overall encoded extension content
nonce.encode(baos);
dumpHexBytes(OCSP_NONCE_DB16);
System.out.println();
dumpHexBytes(baos.toByteArray());
pass = Arrays.equals(baos.toByteArray(), OCSP_NONCE_DB16);
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testSet = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
OCSPNonceExtension nonceByLen = new OCSPNonceExtension(32);
// Set the nonce data to 16 bytes of DEADBEEF
nonceByLen.set(ELEMENT_NONCE, DEADBEEF_16);
byte[] nonceData = (byte[])nonceByLen.get(ELEMENT_NONCE);
if (!Arrays.equals(nonceData, DEADBEEF_16)) {
throw new RuntimeException("Retuned nonce data does not " +
"match expected result");
}
// Now try to set a value using an object that is not a byte
// array
int[] INT_DB_16 = {
0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF
};
try {
nonceByLen.set(ELEMENT_NONCE, INT_DB_16);
throw new RuntimeException("Accepted get() for " +
"unsupported element name");
} catch (IOException ioe) { } // Expected result
// And try setting a value using an unknown element name
try {
nonceByLen.set("FOO", DEADBEEF_16);
throw new RuntimeException("Accepted get() for " +
"unsupported element name");
} catch (IOException ioe) { } // Expected result
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testGet = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
OCSPNonceExtension nonceByLen = new OCSPNonceExtension(32);
// Grab the nonce data by its correct element name
byte[] nonceData = (byte[])nonceByLen.get(ELEMENT_NONCE);
if (nonceData == null || nonceData.length != 32) {
throw new RuntimeException("Unexpected return value from " +
"get() method: either null or incorrect length");
}
// Now try to get any kind of data using an element name that
// doesn't exist for this extension.
try {
nonceByLen.get("FOO");
throw new RuntimeException("Accepted get() for " +
"unsupported element name");
} catch (IOException ioe) { } // Expected result
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testGetElements = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
OCSPNonceExtension nonceByLen = new OCSPNonceExtension(32);
int elementCount = 0;
boolean foundElement = false;
// There should be exactly one element and its name should
// be "nonce"
for (Enumeration<String> elements = nonceByLen.getElements();
elements.hasMoreElements(); elementCount++) {
if (elements.nextElement().equals(ELEMENT_NONCE)) {
foundElement = true;
}
}
if (!foundElement || elementCount != 1) {
throw new RuntimeException("Unexpected or missing " +
"Enumeration element");
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testGetName = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
OCSPNonceExtension nonceByLen = new OCSPNonceExtension(32);
pass = new Boolean(nonceByLen.getName().equals(EXT_NAME));
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testDelete = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
OCSPNonceExtension nonceByLen = new OCSPNonceExtension(32);
// First verify that there's data to begin with
byte[] nonceData = (byte[])nonceByLen.get(ELEMENT_NONCE);
if (nonceData == null || nonceData.length != 32) {
throw new RuntimeException("Unexpected return value from " +
"get() method: either null or incorrect length");
}
// Attempt to delete using an element name that doesn't exist
// for this extension.
try {
nonceByLen.delete("FOO");
throw new RuntimeException("Accepted delete() for " +
"unsupported element name");
} catch (IOException ioe) { } // Expected result
// Now attempt to properly delete the extension data
nonceByLen.delete(ELEMENT_NONCE);
nonceData = (byte[])nonceByLen.get(ELEMENT_NONCE);
if (nonceData != null) {
throw new RuntimeException("Unexpected non-null return");
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
}

View file

@ -0,0 +1,419 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS (ResponderId tests)
*/
import java.io.*;
import java.security.cert.*;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import javax.security.auth.x500.X500Principal;
import sun.security.x509.KeyIdentifier;
import sun.security.provider.certpath.ResponderId;
/*
* NOTE: this test uses Sun private classes which are subject to change.
*/
public class ResponderIdTests {
private static final boolean debug = true;
// Source certificate created with the following command:
// keytool -genkeypair -alias test1 -keyalg rsa -keysize 2048 \
// -validity 7300 -keystore test1.jks \
// -dname "CN=SelfSignedResponder, OU=Validation Services, O=FakeCompany"
private static final String RESP_CERT_1 =
"-----BEGIN CERTIFICATE-----\n" +
"MIIDQzCCAiugAwIBAgIEXTqCCjANBgkqhkiG9w0BAQsFADBSMRQwEgYDVQQKEwtG\n" +
"YWtlQ29tcGFueTEcMBoGA1UECxMTVmFsaWRhdGlvbiBTZXJ2aWNlczEcMBoGA1UE\n" +
"AxMTU2VsZlNpZ25lZFJlc3BvbmRlcjAeFw0xNDA4MTcwNDM2MzBaFw0zNDA4MTIw\n" +
"NDM2MzBaMFIxFDASBgNVBAoTC0Zha2VDb21wYW55MRwwGgYDVQQLExNWYWxpZGF0\n" +
"aW9uIFNlcnZpY2VzMRwwGgYDVQQDExNTZWxmU2lnbmVkUmVzcG9uZGVyMIIBIjAN\n" +
"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApt2Cmw2k9tviLxaxE8aWNuoosWKL\n" +
"h+K4mNcDGKSoiChsqRqeJEnOxijDZqyFwfkaXvpAduFqYjz+Lij2HumvAjHDTui6\n" +
"bGcbsndRDPjvVo1S7f1oWsg7oiA8Lzmjl452S7UNBsDX5Dt1e84Xxwi40B1J2y8D\n" +
"FRPfYRWRlC1Z4kzqkBBa7JhANS+W8KDstFZxL4AwWH/byNwB5dl2j04ohg/Ar54e\n" +
"mu08PIH3hmi0pAu5wn9ariA7UA5lFWRJzvgGXV5J+QVEFuvKmeJ/Q6tU5OBJGw98\n" +
"zjd7F5B0iE+rJHTNF1aGaQfIorz04onV2WjH2VZA18AaMwqlY2br1SBdTQIDAQAB\n" +
"oyEwHzAdBgNVHQ4EFgQUG09HasSTYaTIh/CxxV/rcJV1LvowDQYJKoZIhvcNAQEL\n" +
"BQADggEBAIcUomNpZxGkocIzzybLyeyC6vLF1k0/unuPAHZLDP3o2JTstPhLHOCg\n" +
"FYw1VG2i23pjwKK2x/o80tJAOmW6vowbAPnNmtNIYO3gB/ZGiKeORoGKBCRDNvFa\n" +
"6ZrWxwTzT3EpVwRe7ameES0uP8+S4q2P5LhwMIMw7vGHoOQJgkAh/NUiCli1qRnJ\n" +
"FYd6cHMJJK5gF2FqQ7tdbA26pS06bkIEvil2M5wyKKWOydOa/pr1LgMf9KxljJ8J\n" +
"XlAOO/mGZGkYmWnQaQuBIDyWunWYlhsyCXMa8AScgs0uUeQp19tO7R0f03q/JXoZ\n" +
"1At1gZiMS7SdQaRWP5q+FunAeFWjsFE=\n" +
"-----END CERTIFICATE-----";
private static final String RESP_CERT_1_SUBJ =
"CN=SelfSignedResponder, OU=Validation Services, O=FakeCompany";
private static X509Certificate cert = null;
// The expected DER-encoding for a byName ResponderId derived
// from RESP_CERT_1
private static final byte[] EXP_NAME_ID_BYTES = {
-95, 84, 48, 82, 49, 20, 48, 18,
6, 3, 85, 4, 10, 19, 11, 70,
97, 107, 101, 67, 111, 109, 112, 97,
110, 121, 49, 28, 48, 26, 6, 3,
85, 4, 11, 19, 19, 86, 97, 108,
105, 100, 97, 116, 105, 111, 110, 32,
83, 101, 114, 118, 105, 99, 101, 115,
49, 28, 48, 26, 6, 3, 85, 4,
3, 19, 19, 83, 101, 108, 102, 83,
105, 103, 110, 101, 100, 82, 101, 115,
112, 111, 110, 100, 101, 114
};
// The expected DER-encoding for a byKey ResponderId derived
// from RESP_CERT_1
private static final byte[] EXP_KEY_ID_BYTES = {
-94, 22, 4, 20, 27, 79, 71, 106,
-60, -109, 97, -92, -56, -121, -16, -79,
-59, 95, -21, 112, -107, 117, 46, -6
};
// The DER encoding of a byKey ResponderId, but using an
// incorrect explicit tagging (CONTEXT CONSTRUCTED 3)
private static final byte[] INV_EXPLICIT_TAG_KEY_ID = {
-93, 22, 4, 20, 27, 79, 71, 106,
-60, -109, 97, -92, -56, -121, -16, -79,
-59, 95, -21, 112, -107, 117, 46, -6
};
// These two ResponderId objects will have objects attached to them
// after the pos_CtorByName and pos_CtorByKeyId tests run. Those
// two tests should always be the first two that run.
public static ResponderId respByName;
public static ResponderId respByKeyId;
public static void main(String[] args) throws Exception {
List<TestCase> testList = new ArrayList<>();
testList.add(pos_CtorByName);
testList.add(pos_CtorByKeyId);
testList.add(pos_CtorByEncoding);
testList.add(neg_CtorByEncoding);
testList.add(pos_Equality);
testList.add(pos_GetEncoded);
testList.add(pos_GetRespName);
testList.add(pos_GetRespKeyId);
// Load the certificate object we can use for subsequent tests
CertificateFactory cf = CertificateFactory.getInstance("X.509");
cert = (X509Certificate)cf.generateCertificate(
new ByteArrayInputStream(RESP_CERT_1.getBytes()));
System.out.println("============ Tests ============");
int testNo = 0;
int numberFailed = 0;
Map.Entry<Boolean, String> result;
for (TestCase test : testList) {
System.out.println("Test " + ++testNo + ": " + test.getName());
result = test.runTest();
System.out.print("Result: " + (result.getKey() ? "PASS" : "FAIL"));
System.out.println(" " +
(result.getValue() != null ? result.getValue() : ""));
System.out.println("-------------------------------------------");
if (!result.getKey()) {
numberFailed++;
}
}
System.out.println("End Results: " + (testList.size() - numberFailed) +
" Passed" + ", " + numberFailed + " Failed.");
if (numberFailed > 0) {
throw new RuntimeException(
"One or more tests failed, see test output for details");
}
}
private static void dumpHexBytes(byte[] data) {
if (data != null) {
for (int i = 0; i < data.length; i++) {
if (i % 16 == 0 && i != 0) {
System.out.print("\n");
}
System.out.print(String.format("%02X ", data[i]));
}
System.out.print("\n");
}
}
public interface TestCase {
String getName();
Map.Entry<Boolean, String> runTest();
}
public static final TestCase pos_CtorByName = new TestCase() {
@Override
public String getName() {
return "CTOR Test (by-name)";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
respByName = new ResponderId(cert.getSubjectX500Principal());
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase pos_CtorByKeyId = new TestCase() {
@Override
public String getName() {
return "CTOR Test (by-keyID)";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
respByKeyId = new ResponderId(cert.getPublicKey());
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase pos_CtorByEncoding = new TestCase() {
@Override
public String getName() {
return "CTOR Test (encoded bytes)";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
ResponderId ridByNameBytes = new ResponderId(EXP_NAME_ID_BYTES);
ResponderId ridByKeyIdBytes = new ResponderId(EXP_KEY_ID_BYTES);
if (!ridByNameBytes.equals(respByName)) {
throw new RuntimeException(
"Equals failed: respNameFromBytes vs. respByName");
} else if (!ridByKeyIdBytes.equals(respByKeyId)) {
throw new RuntimeException(
"Equals failed: respKeyFromBytes vs. respByKeyId");
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase neg_CtorByEncoding = new TestCase() {
@Override
public String getName() {
return "CTOR Test (by encoding, unknown explicit tag)";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
ResponderId ridByKeyIdBytes =
new ResponderId(INV_EXPLICIT_TAG_KEY_ID);
throw new RuntimeException("Expected IOException not thrown");
} catch (IOException ioe) {
// Make sure it's the IOException we're looking for
if (ioe.getMessage().contains("Invalid ResponderId content")) {
pass = Boolean.TRUE;
} else {
ioe.printStackTrace(System.out);
message = ioe.getClass().getName();
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase pos_Equality = new TestCase() {
@Override
public String getName() {
return "Simple Equality Test";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
// byName ResponderId equality test
ResponderId compName =
new ResponderId(new X500Principal(RESP_CERT_1_SUBJ));
if (!respByName.equals(compName)) {
message = "ResponderId mismatch in byName comparison";
} else if (respByKeyId.equals(compName)) {
message = "Invalid ResponderId match in byKeyId comparison";
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase pos_GetEncoded = new TestCase() {
@Override
public String getName() {
return "Get Encoded Value";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
// Pull out byName and byKey encodings, they should match
// the expected values
if (!Arrays.equals(respByName.getEncoded(), EXP_NAME_ID_BYTES)) {
message = "ResponderId byName encoding did not " +
"match expected value";
} else if (!Arrays.equals(respByKeyId.getEncoded(), EXP_KEY_ID_BYTES)) {
message = "ResponderId byKeyId encoding did not " +
"match expected value";
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase pos_GetRespName = new TestCase() {
@Override
public String getName() {
return "Get Underlying Responder Name";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
// Test methods for pulling out the underlying
// X500Principal object
X500Principal testPrincipal =
new X500Principal(RESP_CERT_1_SUBJ);
if (!respByName.getResponderName().equals(testPrincipal)) {
message = "ResponderId Name did not match expected value";
} else if (respByKeyId.getResponderName() != null) {
message = "Non-null responder name returned from " +
"ResponderId constructed byKey";
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase pos_GetRespKeyId = new TestCase() {
@Override
public String getName() {
return "Get Underlying Responder Key ID";
}
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
// Test methods for pulling out the underlying
// KeyIdentifier object. Note: There is a minute chance that
// an RSA public key, once hashed into a key ID might collide
// with the one extracted from the certificate used to create
// respByKeyId. This is so unlikely to happen it is considered
// virtually impossible.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair rsaKey = kpg.generateKeyPair();
KeyIdentifier testKeyId = new KeyIdentifier(rsaKey.getPublic());
if (respByKeyId.getKeyIdentifier().equals(testKeyId)) {
message = "Unexpected match in ResponderId Key ID";
} else if (respByName.getKeyIdentifier() != null) {
message = "Non-null key ID returned from " +
"ResponderId constructed byName";
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
}

View file

@ -36,6 +36,6 @@ public class OptimalListSize {
public static void main(String[] args) throws Throwable { public static void main(String[] args) throws Throwable {
OptimalCapacity.ofArrayList( OptimalCapacity.ofArrayList(
Class.forName("sun.security.ssl.ExtensionType"), Class.forName("sun.security.ssl.ExtensionType"),
"knownExtensions", 13); "knownExtensions", 14);
} }
} }

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
final class BogusStatusRequest implements StatusRequest {
BogusStatusRequest() { }
@Override
public int length() { return 0; }
@Override
public void send(HandshakeOutStream s) throws IOException { }
@Override
public String toString() {
return "BogusStatusRequest";
}
}

View file

@ -0,0 +1,346 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS (CertStatusReqExtension tests)
* @build CertStatusReqExtensionTests BogusStatusRequest TestCase TestUtils
* @run main/othervm sun.security.ssl.CertStatusReqExtensionTests
*/
import java.io.IOException;
import java.util.*;
import java.nio.ByteBuffer;
/*
* Checks that the hash value for a certificate's issuer name is generated
* correctly. Requires any certificate that is not self-signed.
*
* NOTE: this test uses Sun private classes which are subject to change.
*/
public class CertStatusReqExtensionTests {
private static final boolean debug = false;
// Default status_request extension (type = ocsp, OCSPStatusRequest
// with no responder IDs or extensions
private static final byte[] CSRE_DEF_OSR = {1, 0, 0, 0, 0};
// A status_request extension using a user-defined type (0xFF) and
// an underlying no-Responder ID/no-extension OCSPStatusRequest
private static final byte[] CSRE_TYPE_FF = {-1, 0, 0, 0, 0};
// A CertStatusReqExtension with 5 ResponderIds and 1 Extension
private static final byte[] CSRE_REQ_RID_EXTS = {
1, 0, -13, 0, 59, -95, 57, 48,
55, 49, 16, 48, 14, 6, 3, 85,
4, 10, 19, 7, 83, 111, 109, 101,
73, 110, 99, 49, 16, 48, 14, 6,
3, 85, 4, 11, 19, 7, 83, 111,
109, 101, 80, 75, 73, 49, 17, 48,
15, 6, 3, 85, 4, 3, 19, 8,
83, 111, 109, 101, 79, 67, 83, 80,
0, 68, -95, 66, 48, 64, 49, 13,
48, 11, 6, 3, 85, 4, 10, 19,
4, 79, 104, 77, 121, 49, 14, 48,
12, 6, 3, 85, 4, 11, 19, 5,
66, 101, 97, 114, 115, 49, 15, 48,
13, 6, 3, 85, 4, 11, 19, 6,
84, 105, 103, 101, 114, 115, 49, 14,
48, 12, 6, 3, 85, 4, 3, 19,
5, 76, 105, 111, 110, 115, 0, 58,
-95, 56, 48, 54, 49, 16, 48, 14,
6, 3, 85, 4, 10, 19, 7, 67,
111, 109, 112, 97, 110, 121, 49, 13,
48, 11, 6, 3, 85, 4, 11, 19,
4, 87, 101, 115, 116, 49, 19, 48,
17, 6, 3, 85, 4, 3, 19, 10,
82, 101, 115, 112, 111, 110, 100, 101,
114, 49, 0, 24, -94, 22, 4, 20,
-67, -36, 114, 121, 92, -79, 116, -1,
102, -107, 7, -21, 18, -113, 64, 76,
96, -7, -66, -63, 0, 24, -94, 22,
4, 20, -51, -69, 107, -82, -39, -87,
45, 25, 41, 28, -76, -68, -11, -110,
-94, -97, 62, 47, 58, -125, 0, 51,
48, 49, 48, 47, 6, 9, 43, 6,
1, 5, 5, 7, 48, 1, 2, 4,
34, 4, 32, -26, -81, -120, -61, -127,
-79, 0, -39, -54, 49, 3, -51, -57,
-85, 19, -126, 94, -2, 21, 26, 98,
6, 105, -35, -37, -29, -73, 101, 53,
44, 15, -19
};
public static void main(String[] args) throws Exception {
Map<String, TestCase> testList =
new LinkedHashMap<String, TestCase>() {{
put("CTOR (default)", testCtorDefault);
put("CTOR (int, StatusRequest)", testCtorStatReqs);
put("CTOR (HandshakeInStream, length, getReqType, getRequest)",
testCtorInStream);
}};
TestUtils.runTests(testList);
}
public static final TestCase testCtorDefault = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
CertStatusReqExtension csreDef = new CertStatusReqExtension();
HandshakeOutStream hsout =
new HandshakeOutStream(null);
csreDef.send(hsout);
TestUtils.valueCheck(wrapExtData(null), hsout.toByteArray());
// The length should be 4 (2 bytes for the type, 2 for the
// encoding of zero-length
if (csreDef.length() != 4) {
throw new RuntimeException("Incorrect length from " +
"default object. Expected 4, got " +
csreDef.length());
}
// Since there's no data, there are no status_type or request
// data fields defined. Both should return null in this case
if (csreDef.getType() != null) {
throw new RuntimeException("Default CSRE returned " +
"non-null status_type");
} else if (csreDef.getRequest() != null) {
throw new RuntimeException("Default CSRE returned " +
"non-null request object");
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testCtorStatReqs = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
HandshakeOutStream hsout =
new HandshakeOutStream(null);
StatusRequest basicStatReq = new OCSPStatusRequest();
// Create an extension using a default-style OCSPStatusRequest
// (no responder IDs, no extensions).
CertStatusReqExtension csre1 = new CertStatusReqExtension(
StatusRequestType.OCSP, basicStatReq);
csre1.send(hsout);
TestUtils.valueCheck(wrapExtData(CSRE_DEF_OSR),
hsout.toByteArray());
hsout.reset();
// Create the extension using a StatusRequestType not already
// instantiated as a static StatusRequestType
// (e.g. OCSP/OCSP_MULTI)
CertStatusReqExtension csre2 =
new CertStatusReqExtension(StatusRequestType.get(-1),
basicStatReq);
csre2.send(hsout);
TestUtils.valueCheck(wrapExtData(CSRE_TYPE_FF),
hsout.toByteArray());
// Create the extension using a StatusRequest that
// does not match the status_type field
// This should throw an IllegalArgumentException
try {
CertStatusReqExtension csreBadRequest =
new CertStatusReqExtension(StatusRequestType.OCSP,
new BogusStatusRequest());
throw new RuntimeException("Constructor accepted a " +
"StatusRequest that is inconsistent with " +
"the status_type");
} catch (IllegalArgumentException iae) { }
// We don't allow a null value for the StatusRequestType
// parameter in this constructor.
try {
CertStatusReqExtension csreBadRequest =
new CertStatusReqExtension(null, basicStatReq);
throw new RuntimeException("Constructor accepted a " +
"null StatusRequestType");
} catch (NullPointerException npe) { }
// We also don't allow a null value for the StatusRequest
// parameter in this constructor.
try {
CertStatusReqExtension csreBadRequest =
new CertStatusReqExtension(StatusRequestType.OCSP,
null);
throw new RuntimeException("Constructor accepted a " +
"null StatusRequest");
} catch (NullPointerException npe) { }
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the constructor that builds the ob ject using data from
// a HandshakeInStream
// This also tests the length, getReqType and getRequest methods
public static final TestCase testCtorInStream = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
OCSPStatusRequest osr;
try {
// To simulate the extension coming in a ServerHello, the
// type and length would already be read by HelloExtensions
// and there is no extension data
HandshakeInStream hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(new byte[0]));
CertStatusReqExtension csre =
new CertStatusReqExtension(hsis, hsis.available());
// Verify length/type/request
if (csre.length() != 4) {
throw new RuntimeException("Invalid length: received " +
csre.length() + ", expected 4");
} else if (csre.getType() != null) {
throw new RuntimeException("Non-null type from default " +
"extension");
} else if (csre.getRequest() != null) {
throw new RuntimeException("Non-null request from default " +
"extension");
}
// Try the an extension with a default OCSPStatusRequest
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(CSRE_DEF_OSR));
csre = new CertStatusReqExtension(hsis, hsis.available());
if (csre.length() != (CSRE_DEF_OSR.length + 4)) {
throw new RuntimeException("Invalid length: received " +
csre.length() + ", expected " +
CSRE_DEF_OSR.length + 4);
} else if (!csre.getType().equals(StatusRequestType.OCSP)) {
throw new RuntimeException("Unknown status_type: " +
String.format("0x%02X", csre.getType().id));
} else {
osr = (OCSPStatusRequest)csre.getRequest();
if (!osr.getResponderIds().isEmpty() ||
!osr.getExtensions().isEmpty()) {
throw new RuntimeException("Non-default " +
"OCSPStatusRequest found in extension");
}
}
// Try with a non-default extension
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(CSRE_REQ_RID_EXTS));
csre = new CertStatusReqExtension(hsis, hsis.available());
if (csre.length() != (CSRE_REQ_RID_EXTS.length + 4)) {
throw new RuntimeException("Invalid length: received " +
csre.length() + ", expected " +
CSRE_REQ_RID_EXTS.length + 4);
} else if (!(csre.getType().equals(StatusRequestType.OCSP))) {
throw new RuntimeException("Unknown status_type: " +
String.format("0x%02X", csre.getType().id));
} else {
osr = (OCSPStatusRequest)csre.getRequest();
if (osr.getResponderIds().size() != 5 ||
osr.getExtensions().size() != 1) {
throw new RuntimeException("Incorrect number of " +
"ResponderIds or Extensions found in " +
"OCSPStatusRequest");
}
}
// Create a CSRE that asserts status_request and has the
// proper length, but really is a bunch of random junk inside
// In this case, it will create an UnknownStatusRequest to
// handle the unparseable data.
byte[] junkData = new byte[48];
Random r = new Random(System.currentTimeMillis());
r.nextBytes(junkData);
junkData[0] = 7; // Ensure it isn't a valid status_type
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(junkData));
csre = new CertStatusReqExtension(hsis, hsis.available());
StatusRequest sr = csre.getRequest();
if (!(sr instanceof UnknownStatusRequest)) {
throw new RuntimeException("Expected returned status " +
"request to be of type UnknownStatusRequest but " +
"received " + sr.getClass().getName());
} else if (csre.length() != (junkData.length + 4)) {
throw new RuntimeException("Invalid length: received " +
csre.length() + ", expected " +
junkData.length + 4);
}
// Set the leading byte to 1 (OCSP type) and run again
// It should pass the argument check and fail trying to parse
// the underlying StatusRequest.
junkData[0] = (byte)StatusRequestType.OCSP.id;
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(junkData));
try {
csre = new CertStatusReqExtension(hsis, hsis.available());
throw new RuntimeException("Expected CTOR exception did " +
"not occur");
} catch (IOException ioe) { }
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Take CSRE extension data and add extension type and length decorations
private static byte[] wrapExtData(byte[] extData) {
int bufferLen = (extData != null ? extData.length : 0) + 4;
ByteBuffer bb = ByteBuffer.allocate(bufferLen);
bb.putShort((short)ExtensionType.EXT_STATUS_REQUEST.id);
bb.putShort((short)(extData != null ? extData.length: 0));
if (extData != null) {
bb.put(extData);
}
return bb.array();
}
}

View file

@ -0,0 +1,463 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS (CertStatusReqItemv2 tests)
* @build CertStatusReqItemV2Tests BogusStatusRequest TestCase TestUtils
* @run main/othervm sun.security.ssl.CertStatusReqItemV2Tests
*/
import java.security.cert.*;
import java.util.*;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLException;
import javax.security.auth.x500.X500Principal;
import sun.security.provider.certpath.ResponderId;
import sun.security.provider.certpath.OCSPNonceExtension;
/*
* Checks that the hash value for a certificate's issuer name is generated
* correctly. Requires any certificate that is not self-signed.
*
* NOTE: this test uses Sun private classes which are subject to change.
*/
public class CertStatusReqItemV2Tests {
private static final boolean debug = false;
private static final byte[] DEF_CSRIV2_OCSP_MULTI_BYTES = {
2, 0, 4, 0, 0, 0, 0
};
private static final byte[] DEF_CSRIV2_OCSP_BYTES = {
1, 0, 4, 0, 0, 0, 0
};
// This is a CSRIV2 (ocsp_multi) that has a single
// responder ID and no extensions.
private static final byte[] CSRIV2_1RID = {
2, 0, 32, 0, 28, 0, 26, -95,
24, 48, 22, 49, 20, 48, 18, 6,
3, 85, 4, 3, 19, 11, 79, 67,
83, 80, 32, 83, 105, 103, 110, 101,
114, 0 , 0
};
// This is a CSRIV2 (ocsp_multi) that has a single
// responder ID and no extensions. The request_length
// field is too short in this case.
private static final byte[] CSRIV2_LENGTH_TOO_SHORT = {
2, 0, 27, 0, 28, 0, 26, -95,
24, 48, 22, 49, 20, 48, 18, 6,
3, 85, 4, 3, 19, 11, 79, 67,
83, 80, 32, 83, 105, 103, 110, 101,
114, 0 , 0
};
// This is a CSRIV2 (ocsp_multi) that has a single
// responder ID and no extensions. The request_length
// field is too long in this case.
private static final byte[] CSRIV2_LENGTH_TOO_LONG = {
2, 0, 54, 0, 28, 0, 26, -95,
24, 48, 22, 49, 20, 48, 18, 6,
3, 85, 4, 3, 19, 11, 79, 67,
83, 80, 32, 83, 105, 103, 110, 101,
114, 0 , 0
};
// A CSRIV2 (ocsp) with one Responder ID (byName: CN=OCSP Signer)
// and a nonce extension (32 bytes).
private static final byte[] CSRIV2_OCSP_1RID_1EXT = {
1, 0, 83, 0, 28, 0, 26, -95,
24, 48, 22, 49, 20, 48, 18, 6,
3, 85, 4, 3, 19, 11, 79, 67,
83, 80, 32, 83, 105, 103, 110, 101,
114, 0, 51, 48, 49, 48, 47, 6,
9, 43, 6, 1, 5, 5, 7, 48,
1, 2, 4, 34, 4, 32, -34, -83,
-66, -17, -34, -83, -66, -17, -34, -83,
-66, -17, -34, -83, -66, -17, -34, -83,
-66, -17, -34, -83, -66, -17, -34, -83,
-66, -17, -34, -83, -66, -17
};
public static void main(String[] args) throws Exception {
Map<String, TestCase> testList =
new LinkedHashMap<String, TestCase>() {{
put("CTOR (Default)", testCtorTypeStatReq);
put("CTOR (Byte array)", testCtorByteArray);
put("CTOR (invalid lengths)", testCtorInvalidLengths);
}};
TestUtils.runTests(testList);
}
public static final TestCase testCtorTypeStatReq = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
// Attempt to create CSRIv2 objects using null pointers
// for either parameter. In either case NPE should be thrown
CertStatusReqItemV2 csriNull;
try {
csriNull = new CertStatusReqItemV2(null,
new OCSPStatusRequest());
throw new RuntimeException("Did not catch expected NPE " +
"for null status_type parameter");
} catch (NullPointerException npe) { }
try {
csriNull = new CertStatusReqItemV2(StatusRequestType.OCSP,
null);
throw new RuntimeException("Did not catch expected NPE " +
"for null StatusRequest parameter");
} catch (NullPointerException npe) { }
// Create an "ocsp_multi" type request using a default
// (no Responder IDs, no Extensions) OCSPStatusRequest
CertStatusReqItemV2 csriMulti =
new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI,
new OCSPStatusRequest());
HandshakeOutStream hsout = new HandshakeOutStream(null);
csriMulti.send(hsout);
TestUtils.valueCheck(DEF_CSRIV2_OCSP_MULTI_BYTES,
hsout.toByteArray());
hsout.reset();
// Create an "ocsp" type request using a default
// (no Responder IDs, no Extensions) OCSPStatusRequest
CertStatusReqItemV2 csriSingle =
new CertStatusReqItemV2(StatusRequestType.OCSP,
new OCSPStatusRequest(new LinkedList<>(),
new LinkedList<>()));
csriSingle.send(hsout);
TestUtils.valueCheck(DEF_CSRIV2_OCSP_BYTES,
hsout.toByteArray());
// Create the CertStatusRequestItemV2 with a user-defined
// StatusRequestType value
CertStatusReqItemV2 csriNine =
new CertStatusReqItemV2(StatusRequestType.get(9),
new OCSPStatusRequest(null, null));
if (csriNine.getType().id != 9) {
throw new RuntimeException("Expected status_type = 9, " +
"got " + csriNine.getType().id);
} else {
StatusRequest sr = csriNine.getRequest();
if (!(sr instanceof OCSPStatusRequest)) {
throw new RuntimeException("Expected " +
"OCSPStatusRequest, got " +
sr.getClass().getName());
}
}
// Create the CertStatusRequestItemV2 with a StatusRequest
// that does not match the status_type argument.
// We expect IllegalArgumentException in this case.
try {
CertStatusReqItemV2 csriBadSR = new CertStatusReqItemV2(
StatusRequestType.OCSP_MULTI,
new BogusStatusRequest());
throw new RuntimeException("Constructor accepted a " +
"StatusRequest that is inconsistent with " +
"the status_type");
} catch (IllegalArgumentException iae) {
// The expected result...nothing to do here
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the constructor form that takes the data from a byte array
public static final TestCase testCtorByteArray = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
StatusRequestType sType;
StatusRequest sReq;
ResponderId checkRid =
new ResponderId(new X500Principal("CN=OCSP Signer"));
Extension checkExt = new OCSPNonceExtension(32);
CertStatusReqItemV2 csriv =
new CertStatusReqItemV2(CSRIV2_OCSP_1RID_1EXT);
sType = csriv.getType();
if (sType != StatusRequestType.OCSP) {
throw new RuntimeException("Unexpected StatusRequestType " +
sType.getClass().getName());
}
sReq = csriv.getRequest();
if (sReq instanceof OCSPStatusRequest) {
OCSPStatusRequest osr = (OCSPStatusRequest)sReq;
List<ResponderId> ridList = osr.getResponderIds();
List<Extension> extList = osr.getExtensions();
if (ridList.size() != 1 || !ridList.contains(checkRid)) {
throw new RuntimeException("Responder list mismatch");
} else if (extList.size() != 1 ||
!extList.get(0).getId().equals(checkExt.getId())) {
throw new RuntimeException("Extension list mismatch");
}
} else {
throw new RuntimeException("Expected OCSPStatusRequest " +
"from decoded bytes, got " +
sReq.getClass().getName());
}
// Create a CSRIV2 out of random data. A non-OCSP/OCSP_MULTI
// type will be forcibly set and the outer length field will
// be correct.
// The constructor should create a StatusRequestType object
// and an UnknownStatusRequest object consisting of the
// data segment.
byte[] junkData = new byte[48];
Random r = new Random(System.currentTimeMillis());
r.nextBytes(junkData);
junkData[0] = 7; // status_type = 7
junkData[1] = 0;
junkData[2] = 45; // request_length = 45
csriv = new CertStatusReqItemV2(junkData);
sType = csriv.getType();
sReq = csriv.getRequest();
if (sType.id != junkData[0]) {
throw new RuntimeException("StatusRequestType mismatch: " +
"expected 7, got " + sType.id);
}
if (sReq instanceof UnknownStatusRequest) {
// Verify the underlying StatusRequest bytes have been
// preserved correctly.
HandshakeOutStream hsout = new HandshakeOutStream(null);
sReq.send(hsout);
byte[] srDataOut = hsout.toByteArray();
TestUtils.valueCheck(srDataOut, junkData, 0, 3,
srDataOut.length);
} else {
throw new RuntimeException("StatusRequest mismatch: " +
"expected UnknownStatusRequest, got " +
sReq.getClass().getName());
}
// Test the parsing of the default OCSP/OCSP_MULTI extensions
// and make sure the underlying StatusRequestType and
// StatusRequest objects are correct.
csriv = new CertStatusReqItemV2(DEF_CSRIV2_OCSP_MULTI_BYTES);
sType = csriv.getType();
sReq = csriv.getRequest();
if (sType != StatusRequestType.OCSP_MULTI) {
throw new RuntimeException("StatusRequestType mismatch: " +
"expected OCSP_MULTI (2), got " + sType.id);
}
if (!(sReq instanceof OCSPStatusRequest)) {
throw new RuntimeException("StatusRequest mismatch: " +
"expected OCSPStatusRequest, got " +
sReq.getClass().getName());
}
csriv = new CertStatusReqItemV2(DEF_CSRIV2_OCSP_BYTES);
sType = csriv.getType();
sReq = csriv.getRequest();
if (sType != StatusRequestType.OCSP) {
throw new RuntimeException("StatusRequestType mismatch: " +
"expected OCSP (1), got " + sType.id);
}
if (!(sReq instanceof OCSPStatusRequest)) {
throw new RuntimeException("StatusRequest mismatch: " +
"expected OCSPStatusRequest, got " +
sReq.getClass().getName());
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testCtorInvalidLengths = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
try {
CertStatusReqItemV2 csriTooShort =
new CertStatusReqItemV2(CSRIV2_LENGTH_TOO_SHORT);
throw new RuntimeException("Expected exception not thrown");
} catch (SSLException ssle) { }
try {
CertStatusReqItemV2 csriTooLong =
new CertStatusReqItemV2(CSRIV2_LENGTH_TOO_LONG);
throw new RuntimeException("Expected exception not thrown");
} catch (SSLException ssle) { }
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the constructor form that takes the data from HandshakeInputStream
public static final TestCase testCtorInputStream = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
StatusRequestType sType;
StatusRequest sReq;
ResponderId checkRid =
new ResponderId(new X500Principal("CN=OCSP Signer"));
Extension checkExt = new OCSPNonceExtension(32);
HandshakeInStream hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(CSRIV2_OCSP_1RID_1EXT));
CertStatusReqItemV2 csriv = new CertStatusReqItemV2(hsis);
sType = csriv.getType();
if (sType != StatusRequestType.OCSP) {
throw new RuntimeException("Unexpected StatusRequestType " +
sType.getClass().getName());
}
sReq = csriv.getRequest();
if (sReq instanceof OCSPStatusRequest) {
OCSPStatusRequest osr = (OCSPStatusRequest)sReq;
List<ResponderId> ridList = osr.getResponderIds();
List<Extension> extList = osr.getExtensions();
if (ridList.size() != 1 || !ridList.contains(checkRid)) {
throw new RuntimeException("Responder list mismatch");
} else if (extList.size() != 1 ||
!extList.get(0).getId().equals(checkExt.getId())) {
throw new RuntimeException("Extension list mismatch");
}
} else {
throw new RuntimeException("Expected OCSPStatusRequest " +
"from decoded bytes, got " +
sReq.getClass().getName());
}
// Create a CSRIV2 out of random data. A non-OCSP/OCSP_MULTI
// type will be forcibly set and the outer length field will
// be correct.
// The constructor should create a StatusRequestType object
// and an UnknownStatusRequest object consisting of the
// data segment.
byte[] junkData = new byte[48];
Random r = new Random(System.currentTimeMillis());
r.nextBytes(junkData);
junkData[0] = 7; // status_type = 7
junkData[1] = 0;
junkData[2] = 45; // request_length = 45
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(junkData));
csriv = new CertStatusReqItemV2(hsis);
sType = csriv.getType();
sReq = csriv.getRequest();
if (sType.id != junkData[0]) {
throw new RuntimeException("StatusRequestType mismatch: " +
"expected 7, got " + sType.id);
}
if (sReq instanceof UnknownStatusRequest) {
// Verify the underlying StatusRequest bytes have been
// preserved correctly.
HandshakeOutStream hsout = new HandshakeOutStream(null);
sReq.send(hsout);
byte[] srDataOut = hsout.toByteArray();
TestUtils.valueCheck(srDataOut, junkData, 0, 3,
srDataOut.length);
} else {
throw new RuntimeException("StatusRequest mismatch: " +
"expected UnknownStatusRequest, got " +
sReq.getClass().getName());
}
// Test the parsing of the default OCSP/OCSP_MULTI extensions
// and make sure the underlying StatusRequestType and
// StatusRequest objects are correct.
hsis = new HandshakeInStream();
hsis.incomingRecord(
ByteBuffer.wrap(DEF_CSRIV2_OCSP_MULTI_BYTES));
csriv = new CertStatusReqItemV2(hsis);
sType = csriv.getType();
sReq = csriv.getRequest();
if (sType != StatusRequestType.OCSP_MULTI) {
throw new RuntimeException("StatusRequestType mismatch: " +
"expected OCSP_MULTI (2), got " + sType.id);
}
if (!(sReq instanceof OCSPStatusRequest)) {
throw new RuntimeException("StatusRequest mismatch: " +
"expected OCSPStatusRequest, got " +
sReq.getClass().getName());
}
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(DEF_CSRIV2_OCSP_BYTES));
csriv = new CertStatusReqItemV2(hsis);
sType = csriv.getType();
sReq = csriv.getRequest();
if (sType != StatusRequestType.OCSP) {
throw new RuntimeException("StatusRequestType mismatch: " +
"expected OCSP (1), got " + sType.id);
}
if (!(sReq instanceof OCSPStatusRequest)) {
throw new RuntimeException("StatusRequest mismatch: " +
"expected OCSPStatusRequest, got " +
sReq.getClass().getName());
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
}

View file

@ -0,0 +1,413 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS (CertStatusReqListV2Extension tests)
* @build CertStatusReqListV2ExtensionTests TestCase TestUtils
* @run main/othervm sun.security.ssl.CertStatusReqListV2ExtensionTests
*/
import java.io.IOException;
import java.util.*;
import java.nio.ByteBuffer;
import javax.net.ssl.*;
/*
* Checks that the hash value for a certificate's issuer name is generated
* correctly. Requires any certificate that is not self-signed.
*
* NOTE: this test uses Sun private classes which are subject to change.
*/
public class CertStatusReqListV2ExtensionTests {
private static final boolean debug = false;
// Default status_request_v2 extension with two items
// 1. Type = ocsp_multi, OCSPStatusRequest is default
// 2. Type = ocsp, OCSPStatusRequest is default
private static final byte[] CSRLV2_DEF = {
0, 14, 2, 0, 4, 0, 0, 0,
0, 1, 0, 4, 0, 0, 0, 0
};
// A status_request_v2 where the item list length is
// longer than the provided data
private static final byte[] CSRLV2_LEN_TOO_LONG = {
0, 18, 2, 0, 4, 0, 0, 0,
0, 1, 0, 4, 0, 0, 0, 0
};
// A status_request_v2 where the item list length is
// shorter than the provided data
private static final byte[] CSRLV2_LEN_TOO_SHORT = {
0, 11, 2, 0, 4, 0, 0, 0,
0, 1, 0, 4, 0, 0, 0, 0
};
// A status_request_v2 extension with a zero-length
// certificate_status_req_list (not allowed by the spec)
private static final byte[] CSRLV2_INVALID_ZEROLEN = {0, 0};
// A status_request_v2 extension with two items (ocsp_multi and ocsp)
// using OCSPStatusRequests with 5 ResponderIds and 1 Extension each.
private static final byte[] CSRLV2_TWO_NON_DEF_ITEMS = {
2, 90, 2, 1, 42, 0, -13, 0,
59, -95, 57, 48, 55, 49, 16, 48,
14, 6, 3, 85, 4, 10, 19, 7,
83, 111, 109, 101, 73, 110, 99, 49,
16, 48, 14, 6, 3, 85, 4, 11,
19, 7, 83, 111, 109, 101, 80, 75,
73, 49, 17, 48, 15, 6, 3, 85,
4, 3, 19, 8, 83, 111, 109, 101,
79, 67, 83, 80, 0, 68, -95, 66,
48, 64, 49, 13, 48, 11, 6, 3,
85, 4, 10, 19, 4, 79, 104, 77,
121, 49, 14, 48, 12, 6, 3, 85,
4, 11, 19, 5, 66, 101, 97, 114,
115, 49, 15, 48, 13, 6, 3, 85,
4, 11, 19, 6, 84, 105, 103, 101,
114, 115, 49, 14, 48, 12, 6, 3,
85, 4, 3, 19, 5, 76, 105, 111,
110, 115, 0, 58, -95, 56, 48, 54,
49, 16, 48, 14, 6, 3, 85, 4,
10, 19, 7, 67, 111, 109, 112, 97,
110, 121, 49, 13, 48, 11, 6, 3,
85, 4, 11, 19, 4, 87, 101, 115,
116, 49, 19, 48, 17, 6, 3, 85,
4, 3, 19, 10, 82, 101, 115, 112,
111, 110, 100, 101, 114, 49, 0, 24,
-94, 22, 4, 20, -67, -36, 114, 121,
92, -79, 116, -1, 102, -107, 7, -21,
18, -113, 64, 76, 96, -7, -66, -63,
0, 24, -94, 22, 4, 20, -51, -69,
107, -82, -39, -87, 45, 25, 41, 28,
-76, -68, -11, -110, -94, -97, 62, 47,
58, -125, 0, 51, 48, 49, 48, 47,
6, 9, 43, 6, 1, 5, 5, 7,
48, 1, 2, 4, 34, 4, 32, -26,
-81, -120, -61, -127, -79, 0, -39, -54,
49, 3, -51, -57, -85, 19, -126, 94,
-2, 21, 26, 98, 6, 105, -35, -37,
-29, -73, 101, 53, 44, 15, -19, 1,
1, 42, 0, -13, 0, 59, -95, 57,
48, 55, 49, 16, 48, 14, 6, 3,
85, 4, 10, 19, 7, 83, 111, 109,
101, 73, 110, 99, 49, 16, 48, 14,
6, 3, 85, 4, 11, 19, 7, 83,
111, 109, 101, 80, 75, 73, 49, 17,
48, 15, 6, 3, 85, 4, 3, 19,
8, 83, 111, 109, 101, 79, 67, 83,
80, 0, 68, -95, 66, 48, 64, 49,
13, 48, 11, 6, 3, 85, 4, 10,
19, 4, 79, 104, 77, 121, 49, 14,
48, 12, 6, 3, 85, 4, 11, 19,
5, 66, 101, 97, 114, 115, 49, 15,
48, 13, 6, 3, 85, 4, 11, 19,
6, 84, 105, 103, 101, 114, 115, 49,
14, 48, 12, 6, 3, 85, 4, 3,
19, 5, 76, 105, 111, 110, 115, 0,
58, -95, 56, 48, 54, 49, 16, 48,
14, 6, 3, 85, 4, 10, 19, 7,
67, 111, 109, 112, 97, 110, 121, 49,
13, 48, 11, 6, 3, 85, 4, 11,
19, 4, 87, 101, 115, 116, 49, 19,
48, 17, 6, 3, 85, 4, 3, 19,
10, 82, 101, 115, 112, 111, 110, 100,
101, 114, 49, 0, 24, -94, 22, 4,
20, -67, -36, 114, 121, 92, -79, 116,
-1, 102, -107, 7, -21, 18, -113, 64,
76, 96, -7, -66, -63, 0, 24, -94,
22, 4, 20, -51, -69, 107, -82, -39,
-87, 45, 25, 41, 28, -76, -68, -11,
-110, -94, -97, 62, 47, 58, -125, 0,
51, 48, 49, 48, 47, 6, 9, 43,
6, 1, 5, 5, 7, 48, 1, 2,
4, 34, 4, 32, -26, -81, -120, -61,
-127, -79, 0, -39, -54, 49, 3, -51,
-57, -85, 19, -126, 94, -2, 21, 26,
98, 6, 105, -35, -37, -29, -73, 101,
53, 44, 15, -19
};
public static void main(String[] args) throws Exception {
Map<String, TestCase> testList =
new LinkedHashMap<String, TestCase>() {{
put("CTOR (default)", testCtorDefault);
put("CTOR (List<CertStatusReqItemV2)", testCtorItemList);
put("CTOR (HandshakeInStream, getRequestList",
testCtorInStream);
}};
TestUtils.runTests(testList);
}
public static final TestCase testCtorDefault = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
CertStatusReqListV2Extension csrlV2 =
new CertStatusReqListV2Extension();
HandshakeOutStream hsout = new HandshakeOutStream(null);
csrlV2.send(hsout);
TestUtils.valueCheck(wrapExtData(new byte[0]),
hsout.toByteArray());
// The length should be 4 (2 bytes for the type, 2 for the
// encoding of zero-length
if (csrlV2.length() != 4) {
throw new RuntimeException("Incorrect length from " +
"default object. Expected 4, got " +
csrlV2.length());
}
// Since there's no data, there are no status_type or request
// data fields defined. An empty, unmodifiable list should be
// returned when obtained from the extension.
List<CertStatusReqItemV2> itemList = csrlV2.getRequestItems();
if (!itemList.isEmpty()) {
throw new RuntimeException("Default CSRLV2 returned " +
"non-empty request list");
} else {
try {
itemList.add(new CertStatusReqItemV2(
StatusRequestType.OCSP_MULTI,
new OCSPStatusRequest()));
throw new RuntimeException("Returned itemList is " +
"modifiable!");
} catch (UnsupportedOperationException uoe) { }
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
public static final TestCase testCtorItemList = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
OCSPStatusRequest osr = new OCSPStatusRequest();
List<CertStatusReqItemV2> noItems = Collections.emptyList();
List<CertStatusReqItemV2> defList =
new ArrayList<CertStatusReqItemV2>() {{
add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, osr));
add(new CertStatusReqItemV2(StatusRequestType.OCSP, osr));
}};
List<CertStatusReqItemV2> unknownTypesList =
new ArrayList<CertStatusReqItemV2>() {{
add(new CertStatusReqItemV2(StatusRequestType.get(8),
new UnknownStatusRequest(new byte[0])));
add(new CertStatusReqItemV2(StatusRequestType.get(12),
new UnknownStatusRequest(new byte[5])));
}};
try {
HandshakeOutStream hsout = new HandshakeOutStream(null);
StatusRequest basicStatReq = new OCSPStatusRequest();
// Create an extension using a default-style OCSPStatusRequest
// (no responder IDs, no extensions).
CertStatusReqListV2Extension csrlv2 =
new CertStatusReqListV2Extension(defList);
csrlv2.send(hsout);
TestUtils.valueCheck(wrapExtData(CSRLV2_DEF),
hsout.toByteArray());
hsout.reset();
// Create the extension using a StatusRequestType not already
// instantiated as a static StatusRequestType
// (e.g. OCSP/OCSP_MULTI)
csrlv2 = new CertStatusReqListV2Extension(unknownTypesList);
List<CertStatusReqItemV2> itemList = csrlv2.getRequestItems();
if (itemList.size() != unknownTypesList.size()) {
throw new RuntimeException("Custom CSRLV2 returned " +
"an incorrect number of items: expected " +
unknownTypesList.size() + ", got " +
itemList.size());
} else {
// Verify that the list is unmodifiable
try {
itemList.add(new CertStatusReqItemV2(
StatusRequestType.OCSP_MULTI,
new OCSPStatusRequest()));
throw new RuntimeException("Returned itemList is " +
"modifiable!");
} catch (UnsupportedOperationException uoe) { }
}
// Pass a null value for the item list. This should throw
// an exception
try {
CertStatusReqListV2Extension csrlv2Null =
new CertStatusReqListV2Extension(null);
throw new RuntimeException("Constructor accepted a " +
"null request list");
} catch (NullPointerException npe) { }
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the constructor that builds the ob ject using data from
// a HandshakeInStream
// This also tests the length, getReqType and getRequest methods
public static final TestCase testCtorInStream = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
OCSPStatusRequest osr;
CertStatusReqListV2Extension csrlv2;
try {
// To simulate the extension coming in a ServerHello, the
// type and length would already be read by HelloExtensions
// and there is no extension data
HandshakeInStream hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(new byte[0]));
csrlv2 = new CertStatusReqListV2Extension(hsis,
hsis.available());
// Verify length/request list
if (csrlv2.length() != 4) {
throw new RuntimeException("Invalid length: received " +
csrlv2.length() + ", expected 4");
} else {
List<CertStatusReqItemV2> itemList =
csrlv2.getRequestItems();
if (!itemList.isEmpty()) {
throw new RuntimeException("Default CSRLV2 returned " +
"non-empty request list");
} else {
try {
itemList.add(new CertStatusReqItemV2(
StatusRequestType.OCSP_MULTI,
new OCSPStatusRequest()));
throw new RuntimeException("Returned itemList is " +
"modifiable!");
} catch (UnsupportedOperationException uoe) { }
}
}
// Try the an extension with our basic client-generated
// status_request_v2 (2 items, ocsp_multi and ocsp, each with
// a default OCSPStatusRequest
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(CSRLV2_DEF));
csrlv2 = new CertStatusReqListV2Extension(hsis,
hsis.available());
if (csrlv2.length() != (CSRLV2_DEF.length + 4)) {
throw new RuntimeException("Invalid length: received " +
csrlv2.length() + ", expected " +
CSRLV2_DEF.length + 4);
} else {
List<CertStatusReqItemV2> itemList =
csrlv2.getRequestItems();
if (itemList.size() != 2) {
throw new RuntimeException("Unexpected number of " +
"items request list, expected 2, got " +
itemList.size());
} else {
try {
itemList.add(new CertStatusReqItemV2(
StatusRequestType.OCSP_MULTI,
new OCSPStatusRequest()));
throw new RuntimeException("Returned itemList is " +
"modifiable!");
} catch (UnsupportedOperationException uoe) { }
}
}
// Try incoming data with an illegal zero-length
// certificate_status_req_list
try {
hsis = new HandshakeInStream();
hsis.incomingRecord(
ByteBuffer.wrap(CSRLV2_INVALID_ZEROLEN));
csrlv2 = new CertStatusReqListV2Extension(hsis,
hsis.available());
throw new RuntimeException("Unxpected successful " +
"object construction");
} catch (SSLException ssle) { }
// Try extensions where the certificate_status_req_list length
// is either too long or too short
try {
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(CSRLV2_LEN_TOO_LONG));
csrlv2 = new CertStatusReqListV2Extension(hsis,
hsis.available());
throw new RuntimeException("Unxpected successful " +
"object construction");
} catch (SSLException ssle) { }
try {
hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(CSRLV2_LEN_TOO_SHORT));
csrlv2 = new CertStatusReqListV2Extension(hsis,
hsis.available());
throw new RuntimeException("Unxpected successful " +
"object construction");
} catch (SSLException ssle) { }
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Take CSRE extension data and add extension type and length decorations
private static byte[] wrapExtData(byte[] extData) {
int bufferLen = extData.length + 4;
ByteBuffer bb = ByteBuffer.allocate(bufferLen);
bb.putShort((short)ExtensionType.EXT_STATUS_REQUEST_V2.id);
bb.putShort((short)extData.length);
if (extData.length != 0) {
bb.put(extData);
}
return bb.array();
}
}

View file

@ -0,0 +1,340 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS (OCSPStatusRequestTests tests)
* @build OCSPStatusRequestTests TestCase TestUtils
* @run main/othervm sun.security.ssl.OCSPStatusRequestTests
*/
import java.security.cert.*;
import java.util.*;
import java.nio.ByteBuffer;
import javax.security.auth.x500.X500Principal;
import sun.security.provider.certpath.ResponderId;
import sun.security.provider.certpath.OCSPNonceExtension;
/*
* Checks that the hash value for a certificate's issuer name is generated
* correctly. Requires any certificate that is not self-signed.
*
* NOTE: this test uses Sun private classes which are subject to change.
*/
public class OCSPStatusRequestTests {
private static final boolean debug = false;
// The default (no Responder IDs or Extensions)
private static final byte[] DEF_OCSPREQ_BYTES = { 0, 0, 0, 0 };
// OCSP Extension with one Responder ID (byName: CN=OCSP Signer) and
// a nonce extension (32 bytes).
private static final byte[] OCSPREQ_1RID_1EXT = {
0, 28, 0, 26, -95, 24, 48, 22,
49, 20, 48, 18, 6, 3, 85, 4,
3, 19, 11, 79, 67, 83, 80, 32,
83, 105, 103, 110, 101, 114, 0, 51,
48, 49, 48, 47, 6, 9, 43, 6,
1, 5, 5, 7, 48, 1, 2, 4,
34, 4, 32, -34, -83, -66, -17, -34,
-83, -66, -17, -34, -83, -66, -17, -34,
-83, -66, -17, -34, -83, -66, -17, -34,
-83, -66, -17, -34, -83, -66, -17, -34,
-83, -66, -17
};
public static void main(String[] args) throws Exception {
Map<String, TestCase> testList =
new LinkedHashMap<String, TestCase>() {{
put("CTOR (default)", testCtorDefault);
put("CTOR (Responder Id and Extension)", testCtorRidsExts);
put("CTOR (HandshakeInStream)", testCtorInStream);
put("CTOR (byte array)", testCtorByteArray);
put("Length tests", testLength);
put("Equals tests", testEquals);
}};
TestUtils.runTests(testList);
}
// Test the default constructor and its encoding
public static final TestCase testCtorDefault = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
// Create a OCSPStatusRequest with a single ResponderId
// and Extension
OCSPStatusRequest osrDefault = new OCSPStatusRequest();
HandshakeOutStream hsout = new HandshakeOutStream(null);
osrDefault.send(hsout);
System.out.println("Encoded Result:");
TestUtils.dumpBytes(hsout.toByteArray());
TestUtils.valueCheck(DEF_OCSPREQ_BYTES, hsout.toByteArray());
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the constructor form that allows the user to specify zero
// or more ResponderId objects and/or Extensions
public static final TestCase testCtorRidsExts = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
List<ResponderId> ridList = new LinkedList<ResponderId>() {{
add(new ResponderId(new X500Principal("CN=OCSP Signer")));
}};
List<Extension> extList = new LinkedList<Extension>() {{
add(new OCSPNonceExtension(32));
}};
// Default-style OCSPStatusRequest using both empty Lists and
// null inputs
OCSPStatusRequest osrDef1 =
new OCSPStatusRequest(new LinkedList<ResponderId>(),
null);
OCSPStatusRequest osrDef2 =
new OCSPStatusRequest(null,
new LinkedList<Extension>());
HandshakeOutStream hsout = new HandshakeOutStream(null);
osrDef1.send(hsout);
System.out.println("Encoded Result:");
TestUtils.dumpBytes(hsout.toByteArray());
TestUtils.valueCheck(DEF_OCSPREQ_BYTES, hsout.toByteArray());
hsout.reset();
osrDef2.send(hsout);
System.out.println("Encoded Result:");
TestUtils.dumpBytes(hsout.toByteArray());
TestUtils.valueCheck(DEF_OCSPREQ_BYTES, hsout.toByteArray());
hsout.reset();
OCSPStatusRequest osrWithItems =
new OCSPStatusRequest(ridList, extList);
osrWithItems.send(hsout);
System.out.println("Encoded Result:");
byte[] encodedData = hsout.toByteArray();
TestUtils.dumpBytes(encodedData);
// Check everything except the last 32 bytes (nonce data)
TestUtils.valueCheck(OCSPREQ_1RID_1EXT, encodedData, 0, 0,
encodedData.length - 32);
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the constructor that builds the ob ject using data from
// a HandshakeInStream
public static final TestCase testCtorInStream = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
ResponderId checkRid =
new ResponderId(new X500Principal("CN=OCSP Signer"));
Extension checkExt = new OCSPNonceExtension(32);
HandshakeInStream hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(OCSPREQ_1RID_1EXT));
OCSPStatusRequest osr = new OCSPStatusRequest(hsis);
List<ResponderId> ridList = osr.getResponderIds();
List<Extension> extList = osr.getExtensions();
if (ridList.size() != 1 || !ridList.contains(checkRid)) {
throw new RuntimeException("Responder list mismatch");
} else if (extList.size() != 1 ||
!extList.get(0).getId().equals(checkExt.getId())) {
throw new RuntimeException("Extension list mismatch");
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the constructor form that takes the data from a byte array
public static final TestCase testCtorByteArray = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
ResponderId checkRid =
new ResponderId(new X500Principal("CN=OCSP Signer"));
Extension checkExt = new OCSPNonceExtension(32);
OCSPStatusRequest osr =
new OCSPStatusRequest(OCSPREQ_1RID_1EXT);
List<ResponderId> ridList = osr.getResponderIds();
List<Extension> extList = osr.getExtensions();
if (ridList.size() != 1 || !ridList.contains(checkRid)) {
throw new RuntimeException("Responder list mismatch");
} else if (extList.size() != 1 ||
!extList.get(0).getId().equals(checkExt.getId())) {
throw new RuntimeException("Extension list mismatch");
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the length functions for both default and non-default
// OCSPStatusRequest objects
public static final TestCase testLength = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
HandshakeInStream hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(OCSPREQ_1RID_1EXT));
OCSPStatusRequest osr = new OCSPStatusRequest(hsis);
OCSPStatusRequest osrDefault = new OCSPStatusRequest();
if (osrDefault.length() != DEF_OCSPREQ_BYTES.length) {
throw new RuntimeException("Invalid length for default: " +
"Expected" + DEF_OCSPREQ_BYTES.length +
", received " + osrDefault.length());
} else if (osr.length() != OCSPREQ_1RID_1EXT.length) {
throw new RuntimeException("Invalid length for default: " +
"Expected" + OCSPREQ_1RID_1EXT.length +
", received " + osr.length());
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test the equals method with default and non-default objects
public static final TestCase testEquals = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
Boolean pass = Boolean.FALSE;
String message = null;
try {
// Make two different lists with the same ResponderId values
// and also make a extension list
List<ResponderId> ridList1 = new LinkedList<ResponderId>() {{
add(new ResponderId(new X500Principal("CN=OCSP Signer")));
}};
List<ResponderId> ridList2 = new LinkedList<ResponderId>() {{
add(new ResponderId(new X500Principal("CN=OCSP Signer")));
}};
List<Extension> extList = new LinkedList<Extension>() {{
add(new OCSPNonceExtension(32));
}};
// We expect two default OCSP objects to be equal
OCSPStatusRequest osrDefault = new OCSPStatusRequest();
if (!osrDefault.equals(new OCSPStatusRequest())) {
throw new RuntimeException("Default OCSPStatusRequest" +
" equality test failed");
}
// null test (expect false return)
if (osrDefault.equals(null)) {
throw new RuntimeException("OCSPStatusRequest matched" +
" unexpectedly");
}
// Self-reference test
OCSPStatusRequest osrSelfRef = osrDefault;
if (!osrDefault.equals(osrSelfRef)) {
throw new RuntimeException("Default OCSPStatusRequest" +
" equality test failed");
}
// Two OCSPStatusRequests with matching ResponderIds should
// be considered equal
OCSPStatusRequest osrByList1 =
new OCSPStatusRequest(ridList1, null);
OCSPStatusRequest osrByList2 = new OCSPStatusRequest(ridList2,
Collections.emptyList());
if (!osrByList1.equals(osrByList2)) {
throw new RuntimeException("Single Responder ID " +
"OCSPStatusRequest equality test failed");
}
// We expect OCSPStatusRequests with different nonces to be
// considered unequal.
HandshakeInStream hsis = new HandshakeInStream();
hsis.incomingRecord(ByteBuffer.wrap(OCSPREQ_1RID_1EXT));
OCSPStatusRequest osrStream = new OCSPStatusRequest(hsis);
OCSPStatusRequest osrRidExt = new OCSPStatusRequest(ridList1,
extList);
if (osrStream.equals(osrRidExt)) {
throw new RuntimeException("OCSPStatusRequest matched" +
" unexpectedly");
}
pass = Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
}

View file

@ -0,0 +1,455 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
/*
* @test
* @bug 8046321
* @summary OCSP Stapling for TLS (StatusResponseManager tests)
* @library ../../../../java/security/testlibrary
* @build CertificateBuilder SimpleOCSPServer
* @build StatusResponseManagerTests TestCase TestUtils
* @run main/othervm -Djavax.net.debug=ssl:respmgr
* sun.security.ssl.StatusResponseManagerTests
*/
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.*;
import java.util.*;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PublicKey;
import java.util.concurrent.TimeUnit;
import sun.security.testlibrary.SimpleOCSPServer;
import sun.security.testlibrary.CertificateBuilder;
/*
* Checks that the hash value for a certificate's issuer name is generated
* correctly. Requires any certificate that is not self-signed.
*
* NOTE: this test uses Sun private classes which are subject to change.
*/
public class StatusResponseManagerTests {
private static final boolean debug = true;
private static final boolean ocspDebug = false;
// PKI components we will need for this test
static String passwd = "passphrase";
static String ROOT_ALIAS = "root";
static String INT_ALIAS = "intermediate";
static String SSL_ALIAS = "ssl";
static KeyStore rootKeystore; // Root CA Keystore
static KeyStore intKeystore; // Intermediate CA Keystore
static KeyStore serverKeystore; // SSL Server Keystore
static KeyStore trustStore; // SSL Client trust store
static X509Certificate rootCert;
static X509Certificate intCert;
static X509Certificate sslCert;
static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder
static int rootOcspPort; // Port number for root OCSP
static SimpleOCSPServer intOcsp; // Intermediate CA OCSP Responder
static int intOcspPort; // Port number for intermed. OCSP
static X509Certificate[] chain;
public static void main(String[] args) throws Exception {
Map<String, TestCase> testList =
new LinkedHashMap<String, TestCase>() {{
put("Basic OCSP fetch test", testOcspFetch);
put("Clear StatusResponseManager cache", testClearSRM);
put("Basic OCSP_MULTI fetch test", testOcspMultiFetch);
put("Test Cache Expiration", testCacheExpiry);
}};
// Create the CAs and OCSP responders
createPKI();
// Grab the certificates and make a chain we can reuse for tests
sslCert = (X509Certificate)serverKeystore.getCertificate(SSL_ALIAS);
intCert = (X509Certificate)intKeystore.getCertificate(INT_ALIAS);
rootCert = (X509Certificate)rootKeystore.getCertificate(ROOT_ALIAS);
chain = new X509Certificate[3];
chain[0] = sslCert;
chain[1] = intCert;
chain[2] = rootCert;
TestUtils.runTests(testList);
intOcsp.stop();
rootOcsp.stop();
}
// Test a simple RFC 6066 server-side fetch
public static final TestCase testOcspFetch = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
StatusResponseManager srm = new StatusResponseManager();
Boolean pass = Boolean.FALSE;
String message = null;
StatusRequest oReq = new OCSPStatusRequest();
try {
// Get OCSP responses for non-root certs in the chain
Map<X509Certificate, byte[]> responseMap = srm.get(
StatusRequestType.OCSP, oReq, chain, 5000,
TimeUnit.MILLISECONDS);
// There should be one entry in the returned map and
// one entry in the cache when the operation is complete.
if (responseMap.size() != 1) {
message = "Incorrect number of responses: expected 1, got "
+ responseMap.size();
} else if (!responseMap.containsKey(sslCert)) {
message = "Response map key is incorrect, expected " +
sslCert.getSubjectX500Principal().toString();
} else if (srm.size() != 1) {
message = "Incorrect number of cache entries: " +
"expected 1, got " + srm.size();
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test clearing the StatusResponseManager cache.
public static final TestCase testClearSRM = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
StatusResponseManager srm = new StatusResponseManager();
Boolean pass = Boolean.FALSE;
String message = null;
StatusRequest oReq = new OCSPStatusRequest();
try {
// Get OCSP responses for non-root certs in the chain
srm.get(StatusRequestType.OCSP_MULTI, oReq, chain, 5000,
TimeUnit.MILLISECONDS);
// There should be two entries in the returned map and
// two entries in the cache when the operation is complete.
if (srm.size() != 2) {
message = "Incorrect number of responses: expected 2, got "
+ srm.size();
} else {
// Next, clear the SRM, then check the size again
srm.clear();
if (srm.size() != 0) {
message = "Incorrect number of responses: expected 0," +
" got " + srm.size();
} else {
pass = Boolean.TRUE;
}
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test a simple RFC 6961 server-side fetch
public static final TestCase testOcspMultiFetch = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
StatusResponseManager srm = new StatusResponseManager();
Boolean pass = Boolean.FALSE;
String message = null;
StatusRequest oReq = new OCSPStatusRequest();
try {
// Get OCSP responses for non-root certs in the chain
Map<X509Certificate, byte[]> responseMap = srm.get(
StatusRequestType.OCSP_MULTI, oReq, chain, 5000,
TimeUnit.MILLISECONDS);
// There should be two entries in the returned map and
// two entries in the cache when the operation is complete.
if (responseMap.size() != 2) {
message = "Incorrect number of responses: expected 2, got "
+ responseMap.size();
} else if (!responseMap.containsKey(sslCert) ||
!responseMap.containsKey(intCert)) {
message = "Response map keys are incorrect, expected " +
sslCert.getSubjectX500Principal().toString() +
" and " +
intCert.getSubjectX500Principal().toString();
} else if (srm.size() != 2) {
message = "Incorrect number of cache entries: " +
"expected 2, got " + srm.size();
} else {
pass = Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
// Test cache expiration
public static final TestCase testCacheExpiry = new TestCase() {
@Override
public Map.Entry<Boolean, String> runTest() {
// For this test, we will set the cache expiry to 5 seconds
System.setProperty("jdk.tls.stapling.cacheLifetime", "5");
StatusResponseManager srm = new StatusResponseManager();
Boolean pass = Boolean.FALSE;
String message = null;
StatusRequest oReq = new OCSPStatusRequest();
try {
// Get OCSP responses for non-root certs in the chain
srm.get(StatusRequestType.OCSP_MULTI, oReq, chain, 5000,
TimeUnit.MILLISECONDS);
// There should be two entries in the returned map and
// two entries in the cache when the operation is complete.
if (srm.size() != 2) {
message = "Incorrect number of responses: expected 2, got "
+ srm.size();
} else {
// Next, wait for more than 5 seconds so the responses
// in the SRM will expire.
Thread.sleep(7000);
if (srm.size() != 0) {
message = "Incorrect number of responses: expected 0," +
" got " + srm.size();
} else {
pass = Boolean.TRUE;
}
}
} catch (Exception e) {
e.printStackTrace(System.out);
message = e.getClass().getName();
}
// Set the cache lifetime back to the default
System.setProperty("jdk.tls.stapling.cacheLifetime", "");
return new AbstractMap.SimpleEntry<>(pass, message);
}
};
/**
* Creates the PKI components necessary for this test, including
* Root CA, Intermediate CA and SSL server certificates, the keystores
* for each entity, a client trust store, and starts the OCSP responders.
*/
private static void createPKI() throws Exception {
CertificateBuilder cbld = new CertificateBuilder();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyStore.Builder keyStoreBuilder =
KeyStore.Builder.newInstance("PKCS12", null,
new KeyStore.PasswordProtection(passwd.toCharArray()));
// Generate Root, IntCA, EE keys
KeyPair rootCaKP = keyGen.genKeyPair();
log("Generated Root CA KeyPair");
KeyPair intCaKP = keyGen.genKeyPair();
log("Generated Intermediate CA KeyPair");
KeyPair sslKP = keyGen.genKeyPair();
log("Generated SSL Cert KeyPair");
// Set up the Root CA Cert
cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany");
cbld.setPublicKey(rootCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("1"));
// Make a 3 year validity starting from 60 days ago
long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
long end = start + TimeUnit.DAYS.toMillis(1085);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
// Make our Root CA Cert!
X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Root CA Created:\n" + certInfo(rootCert));
// Now build a keystore and add the keys and cert
rootKeystore = keyStoreBuilder.getKeyStore();
Certificate[] rootChain = {rootCert};
rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(),
passwd.toCharArray(), rootChain);
// Now fire up the OCSP responder
rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null);
rootOcsp.enableLog(ocspDebug);
rootOcsp.setNextUpdateInterval(3600);
rootOcsp.start();
Thread.sleep(1000); // Give the server a second to start up
rootOcspPort = rootOcsp.getPort();
String rootRespURI = "http://localhost:" + rootOcspPort;
log("Root OCSP Responder URI is " + rootRespURI);
// Now that we have the root keystore and OCSP responder we can
// create our intermediate CA.
cbld.reset();
cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany");
cbld.setPublicKey(intCaKP.getPublic());
cbld.setSerialNumber(new BigInteger("100"));
// Make a 2 year validity starting from 30 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30);
end = start + TimeUnit.DAYS.toMillis(730);
cbld.setValidity(new Date(start), new Date(end));
addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic());
addCommonCAExts(cbld);
cbld.addAIAExt(Collections.singletonList(rootRespURI));
// Make our Intermediate CA Cert!
X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(),
"SHA256withRSA");
log("Intermediate CA Created:\n" + certInfo(intCaCert));
// Provide intermediate CA cert revocation info to the Root CA
// OCSP responder.
Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
new HashMap<>();
revInfo.put(intCaCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
rootOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
intKeystore = keyStoreBuilder.getKeyStore();
Certificate[] intChain = {intCaCert, rootCert};
intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(),
passwd.toCharArray(), intChain);
intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// Now fire up the Intermediate CA OCSP responder
intOcsp = new SimpleOCSPServer(intKeystore, passwd,
INT_ALIAS, null);
intOcsp.enableLog(ocspDebug);
intOcsp.setNextUpdateInterval(3600);
intOcsp.start();
Thread.sleep(1000);
intOcspPort = intOcsp.getPort();
String intCaRespURI = "http://localhost:" + intOcspPort;
log("Intermediate CA OCSP Responder URI is " + intCaRespURI);
// Last but not least, let's make our SSLCert and add it to its own
// Keystore
cbld.reset();
cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany");
cbld.setPublicKey(sslKP.getPublic());
cbld.setSerialNumber(new BigInteger("4096"));
// Make a 1 year validity starting from 7 days ago
start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
end = start + TimeUnit.DAYS.toMillis(365);
cbld.setValidity(new Date(start), new Date(end));
// Add extensions
addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic());
boolean[] kuBits = {true, false, true, false, false, false,
false, false, false};
cbld.addKeyUsageExt(kuBits);
List<String> ekuOids = new ArrayList<>();
ekuOids.add("1.3.6.1.5.5.7.3.1");
ekuOids.add("1.3.6.1.5.5.7.3.2");
cbld.addExtendedKeyUsageExt(ekuOids);
cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost"));
cbld.addAIAExt(Collections.singletonList(intCaRespURI));
// Make our SSL Server Cert!
X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(),
"SHA256withRSA");
log("SSL Certificate Created:\n" + certInfo(sslCert));
// Provide SSL server cert revocation info to the Intermeidate CA
// OCSP responder.
revInfo = new HashMap<>();
revInfo.put(sslCert.getSerialNumber(),
new SimpleOCSPServer.CertStatusInfo(
SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
intOcsp.updateStatusDb(revInfo);
// Now build a keystore and add the keys, chain and root cert as a TA
serverKeystore = keyStoreBuilder.getKeyStore();
Certificate[] sslChain = {sslCert, intCaCert, rootCert};
serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(),
passwd.toCharArray(), sslChain);
serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
// And finally a Trust Store for the client
trustStore = keyStoreBuilder.getKeyStore();
trustStore.setCertificateEntry(ROOT_ALIAS, rootCert);
}
private static void addCommonExts(CertificateBuilder cbld,
PublicKey subjKey, PublicKey authKey) throws IOException {
cbld.addSubjectKeyIdExt(subjKey);
cbld.addAuthorityKeyIdExt(authKey);
}
private static void addCommonCAExts(CertificateBuilder cbld)
throws IOException {
cbld.addBasicConstraintsExt(true, true, -1);
// Set key usage bits for digitalSignature, keyCertSign and cRLSign
boolean[] kuBitSettings = {true, false, false, false, false, true,
true, false, false};
cbld.addKeyUsageExt(kuBitSettings);
}
/**
* Helper routine that dumps only a few cert fields rather than
* the whole toString() output.
*
* @param cert An X509Certificate to be displayed
*
* @return The {@link String} output of the issuer, subject and
* serial number
*/
private static String certInfo(X509Certificate cert) {
StringBuilder sb = new StringBuilder();
sb.append("Issuer: ").append(cert.getIssuerX500Principal()).
append("\n");
sb.append("Subject: ").append(cert.getSubjectX500Principal()).
append("\n");
sb.append("Serial: ").append(cert.getSerialNumber()).append("\n");
return sb.toString();
}
/**
* Log a message on stdout
*
* @param message The message to log
*/
private static void log(String message) {
if (debug) {
System.out.println(message);
}
}
}

View file

@ -0,0 +1 @@
bootclasspath.dirs=.

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.util.Map;
public interface TestCase {
Map.Entry<Boolean, String> runTest();
}

View file

@ -0,0 +1,126 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
public class TestUtils {
// private constructor to prevent instantiation for this utility class
private TestUtils() {
throw new AssertionError();
}
public static void runTests(Map<String, TestCase> testList) {
int testNo = 0;
int numberFailed = 0;
Map.Entry<Boolean, String> result;
System.out.println("============ Tests ============");
for (String testName : testList.keySet()) {
System.out.println("Test " + ++testNo + ": " + testName);
result = testList.get(testName).runTest();
System.out.print("Result: " + (result.getKey() ? "PASS" : "FAIL"));
System.out.println(" " +
(result.getValue() != null ? result.getValue() : ""));
System.out.println("-------------------------------------------");
if (!result.getKey()) {
numberFailed++;
}
}
System.out.println("End Results: " + (testList.size() - numberFailed) +
" Passed" + ", " + numberFailed + " Failed.");
if (numberFailed > 0) {
throw new RuntimeException(
"One or more tests failed, see test output for details");
}
}
public static void dumpBytes(byte[] data) {
dumpBytes(ByteBuffer.wrap(data));
}
public static void dumpBytes(ByteBuffer data) {
int i = 0;
data.mark();
while (data.hasRemaining()) {
if (i % 16 == 0 && i != 0) {
System.out.print("\n");
}
System.out.print(String.format("%02X ", data.get()));
i++;
}
System.out.print("\n");
data.reset();
}
public static void valueCheck(byte[] array1, byte[] array2) {
if (!Arrays.equals(array1, array2)) {
throw new RuntimeException("Array mismatch");
}
}
// Compares a range of bytes at specific offsets in each array
public static void valueCheck(byte[] array1, byte[] array2, int skip1,
int skip2, int length) {
ByteBuffer buf1 = ByteBuffer.wrap(array1);
ByteBuffer buf2 = ByteBuffer.wrap(array2);
// Skip past however many bytes are requested in both buffers
buf1.position(skip1);
buf2.position(skip2);
// Reset the limits on each buffer to account for the length
buf1.limit(buf1.position() + length);
buf2.limit(buf2.position() + length);
if (!buf1.equals(buf2)) {
throw new RuntimeException("Array range mismatch");
}
}
// Concatenate 1 or more arrays
public static byte[] gatherBuffers(byte[]... arrays) {
int totalLength = 0;
for (byte[] ar : arrays) {
totalLength += ar != null ? ar.length : 0;
}
byte[] resultBuf = new byte[totalLength];
int offset = 0;
for (byte[] ar : arrays) {
if (ar != null) {
System.arraycopy(ar, 0, resultBuf, offset, ar.length);
offset += ar.length;
}
}
return resultBuf;
}
}