8046321: OCSP Stapling for TLS
Summary: Initial feature commit for OCSP stapling in JSSE
Reviewed-by: xuelei, mullan
--- a/jdk/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java Wed Aug 05 12:19:38 2015 -0700
@@ -115,4 +115,45 @@
public List<SNIServerName> getRequestedServerNames() {
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();
+ }
}
--- a/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSP.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSP.java Wed Aug 05 12:19:38 2015 -0700
@@ -42,14 +42,13 @@
import java.util.List;
import java.util.Map;
-import static sun.security.provider.certpath.OCSPResponse.*;
import sun.security.action.GetIntegerAction;
import sun.security.util.Debug;
-import sun.security.util.ObjectIdentifier;
import sun.security.x509.AccessDescription;
import sun.security.x509.AuthorityInfoAccessExtension;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNameInterface;
+import sun.security.x509.PKIXExtensions;
import sun.security.x509.URIName;
import sun.security.x509.X509CertImpl;
@@ -65,9 +64,6 @@
*/
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 int DEFAULT_CONNECT_TIMEOUT = 15000;
@@ -184,12 +180,15 @@
/**
* 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 issuerCert the issuer's certificate
* @param responderCert 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.
+ * @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
* @throws IOException if there is an exception connecting to or
* communicating with the OCSP responder
@@ -202,19 +201,54 @@
List<Extension> extensions)
throws IOException, CertPathValidatorException
{
- byte[] bytes = null;
- OCSPRequest request = null;
+ byte[] nonce = null;
+ for (Extension ext : extensions) {
+ if (ext.getId().equals(PKIXExtensions.OCSPNonce_Id.toString())) {
+ nonce = ext.getValue();
+ }
+ }
+
+ OCSPResponse ocspResponse = null;
try {
- request = new OCSPRequest(certIds, extensions);
- bytes = request.encodeBytes();
+ 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
- ("Exception while encoding OCSPRequest", 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;
OutputStream out = null;
byte[] response = null;
+
try {
URL url = responderURI.toURL();
if (debug != null) {
@@ -257,10 +291,6 @@
}
}
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 {
if (in != null) {
try {
@@ -277,20 +307,7 @@
}
}
}
-
- 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;
+ return response;
}
/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPNonceExtension.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPRequest.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPRequest.java Wed Aug 05 12:19:38 2015 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -32,10 +32,11 @@
import sun.misc.HexDumpEncoder;
import sun.security.util.*;
+import sun.security.x509.PKIXExtensions;
/**
* 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 ASN.1 definition is as follows:
* <pre>
@@ -118,7 +119,8 @@
DerOutputStream extOut = new DerOutputStream();
for (Extension ext : extensions) {
ext.encode(extOut);
- if (ext.getId().equals(OCSP.NONCE_EXTENSION_OID.toString())) {
+ if (ext.getId().equals(
+ PKIXExtensions.OCSPNonce_Id.toString())) {
nonce = ext.getValue();
}
}
--- a/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPResponse.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPResponse.java Wed Aug 05 12:19:38 2015 -0700
@@ -41,6 +41,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.security.auth.x500.X500Principal;
import sun.misc.HexDumpEncoder;
@@ -129,7 +130,7 @@
SIG_REQUIRED, // Must sign the request
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 boolean dump = debug != null && Debug.isOn("ocsp");
@@ -173,7 +174,7 @@
}
// 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 Map<CertId, SingleResponse> singleResponseMap;
@@ -183,13 +184,16 @@
private final byte[] responseNonce;
private List<X509CertImpl> certs;
private X509CertImpl signerCert = null;
- private X500Principal responderName = null;
- private KeyIdentifier responderKeyId = null;
+ private final ResponderId respId;
+ private Date producedAtDate = null;
+ private final Map<String, java.security.cert.Extension> responseExtensions;
/*
* 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) {
HexDumpEncoder hexEnc = new HexDumpEncoder();
debug.println("OCSPResponse bytes...\n\n" +
@@ -221,6 +225,8 @@
signature = null;
tbsResponseData = null;
responseNonce = null;
+ responseExtensions = Collections.emptyMap();
+ respId = null;
return;
}
@@ -239,7 +245,7 @@
// responseType
derIn = tmp.data;
ObjectIdentifier responseType = derIn.getOID();
- if (responseType.equals(OCSP_BASIC_RESPONSE_OID)) {
+ if (responseType.equals((Object)OCSP_BASIC_RESPONSE_OID)) {
if (debug != null) {
debug.println("OCSP response type: basic");
}
@@ -289,27 +295,15 @@
}
// responderID
- short tag = (byte)(seq.tag & 0x1f);
- if (tag == NAME_TAG) {
- responderName = new X500Principal(seq.getData().toByteArray());
- if (debug != null) {
- debug.println("Responder's name: " + responderName);
- }
- } 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");
+ respId = new ResponderId(seq.toByteArray());
+ if (debug != null) {
+ debug.println("Responder ID: " + respId);
}
// producedAt
seq = seqDerIn.getDerValue();
+ producedAtDate = seq.getGeneralizedTime();
if (debug != null) {
- Date producedAtDate = seq.getGeneralizedTime();
debug.println("OCSP response produced at: " + producedAtDate);
}
@@ -320,36 +314,29 @@
debug.println("OCSP number of SingleResponses: "
+ singleResponseDer.length);
}
- for (int i = 0; i < singleResponseDer.length; i++) {
- SingleResponse singleResponse =
- new SingleResponse(singleResponseDer[i]);
+ for (DerValue srDer : singleResponseDer) {
+ SingleResponse singleResponse = new SingleResponse(srDer);
singleResponseMap.put(singleResponse.getCertId(), singleResponse);
}
// responseExtensions
- byte[] nonce = null;
+ Map<String, java.security.cert.Extension> tmpExtMap = new HashMap<>();
if (seqDerIn.available() > 0) {
seq = seqDerIn.getDerValue();
if (seq.isContextSpecific((byte)1)) {
- DerValue[] responseExtDer = seq.data.getSequence(3);
- 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());
- }
- }
+ tmpExtMap = parseExtensions(seq);
}
}
- responseNonce = nonce;
+ 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));
+ }
// signatureAlgorithmId
sigAlgId = AlgorithmId.parse(seqTmp[1]);
@@ -436,20 +423,22 @@
"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) {
- if (cert.getSubjectX500Principal().equals(responderName)) {
+ if (cert.getSubjectX500Principal().equals(rName)) {
signerCert = cert;
break;
}
}
- } else if (responderKeyId != null) {
+ } else if (respId.getType() == ResponderId.Type.BY_KEY) {
+ KeyIdentifier ridKeyId = respId.getKeyIdentifier();
for (X509CertImpl cert : certs) {
// Match responder's key identifier against the cert's SKID
// This will match if the SKID is encoded using the 160-bit
// SHA-1 hash method as defined in RFC 5280.
KeyIdentifier certKeyId = cert.getSubjectKeyId();
- if (certKeyId != null && responderKeyId.equals(certKeyId)) {
+ if (certKeyId != null && ridKeyId.equals(certKeyId)) {
signerCert = cert;
break;
} else {
@@ -463,7 +452,7 @@
} catch (IOException e) {
// ignore
}
- if (responderKeyId.equals(certKeyId)) {
+ if (ridKeyId.equals(certKeyId)) {
signerCert = cert;
break;
}
@@ -592,7 +581,6 @@
}
// Check freshness of OCSPResponse
-
long now = (date == null) ? System.currentTimeMillis() : date.getTime();
Date nowPlusSkew = new Date(now + MAX_CLOCK_SKEW);
Date nowMinusSkew = new Date(now - MAX_CLOCK_SKEW);
@@ -603,16 +591,16 @@
until = " until " + sr.nextUpdate;
}
debug.println("OCSP response validity interval is from " +
- sr.thisUpdate + until);
+ sr.thisUpdate + until);
debug.println("Checking validity of OCSP response on: " +
- new Date(now));
+ new Date(now));
}
// Check that the test date is within the validity interval:
// [ thisUpdate - MAX_CLOCK_SKEW,
// MAX(thisUpdate, nextUpdate) + MAX_CLOCK_SKEW ]
if (nowPlusSkew.before(sr.thisUpdate) ||
- nowMinusSkew.after(
+ nowMinusSkew.after(
sr.nextUpdate != null ? sr.nextUpdate : sr.thisUpdate))
{
throw new CertPathValidatorException(
@@ -624,8 +612,10 @@
/**
* Returns the OCSP ResponseStatus.
+ *
+ * @return the {@code ResponseStatus} for this OCSP response
*/
- ResponseStatus getResponseStatus() {
+ public ResponseStatus getResponseStatus() {
return responseStatus;
}
@@ -663,11 +653,27 @@
/**
* Returns the SingleResponse of the specified CertId, or null if
* 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 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.
*/
@@ -676,11 +682,52 @@
}
/**
+ * 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.
* @param derVal A {@code DerValue} object built from a SEQUENCE of
* 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
* an empty {@code Map} will be returned.
*
@@ -694,6 +741,9 @@
for (DerValue extDerVal : extDer) {
Extension ext = new Extension(extDerVal);
+ if (debug != null) {
+ debug.println("Extension: " + ext);
+ }
// We don't support any extensions yet. Therefore, if it
// is critical we must throw an exception because we
// don't know how to process it.
@@ -710,7 +760,7 @@
/*
* 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 CertStatus certStatus;
private final Date thisUpdate;
@@ -825,23 +875,72 @@
/*
* Return the certificate's revocation status code
*/
- @Override public CertStatus getCertStatus() {
+ @Override
+ public CertStatus getCertStatus() {
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;
}
- @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() :
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;
}
+ /**
+ * 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
public Map<String, java.security.cert.Extension> getSingleExtensions() {
return Collections.unmodifiableMap(singleExtensions);
@@ -852,19 +951,22 @@
*/
@Override public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("SingleResponse: \n");
+ sb.append("SingleResponse:\n");
sb.append(certId);
- sb.append("\nCertStatus: "+ certStatus + "\n");
+ sb.append("\nCertStatus: ").append(certStatus).append("\n");
if (certStatus == CertStatus.REVOKED) {
- sb.append("revocationTime is " + revocationTime + "\n");
- sb.append("revocationReason is " + revocationReason + "\n");
+ sb.append("revocationTime is ");
+ 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) {
- sb.append("nextUpdate is " + nextUpdate + "\n");
+ sb.append("nextUpdate is ").append(nextUpdate).append("\n");
}
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();
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/ResponderId.java Wed Aug 05 12:19:38 2015 -0700
@@ -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();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/CertStatusReqExtension.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/CertStatusReqItemV2.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/CertStatusReqListV2Extension.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+ }
+
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java Wed Aug 05 12:19:38 2015 -0700
@@ -37,10 +37,12 @@
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
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.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.*;
@@ -79,6 +81,12 @@
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
* ClientHello that was used on this handshake. This represents
@@ -200,7 +208,16 @@
@Override
void processMessage(byte type, int messageLen) throws IOException {
// 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) {
case HandshakeMessage.ht_hello_request:
@@ -241,8 +258,19 @@
CertificateMsg certificateMsg = new CertificateMsg(input);
handshakeState.update(certificateMsg, resumingSession);
this.serverCertificate(certificateMsg);
- serverKey =
- session.getPeerCertificates()[0].getPublicKey();
+ if (!staplingActive) {
+ // 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;
case HandshakeMessage.ht_server_key_exchange:
@@ -685,10 +713,22 @@
ExtensionType type = ext.type;
if (type == ExtensionType.EXT_SERVER_NAME) {
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)
&& (type != ExtensionType.EXT_EC_POINT_FORMATS)
&& (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,
"Server sent an unsupported extension: " + type);
}
@@ -1476,6 +1516,12 @@
}
}
+ // Add status_request and status_request_v2 extensions
+ if (enableStatusRequestExtension) {
+ clientHelloMessage.addCertStatusReqListV2Extension();
+ clientHelloMessage.addCertStatusRequestExtension();
+ }
+
// reset the client random cookie
clnt_random = clientHelloMessage.clnt_random;
@@ -1545,40 +1591,36 @@
}
// ask the trust manager to verify the chain
- X509TrustManager tm = sslContext.getX509TrustManager();
- try {
- // 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;
- }
+ if (staplingActive) {
+ // Defer the certificate check until after we've received the
+ // CertificateStatus message. If that message doesn't come in
+ // immediately following this message we will execute the check
+ // directly from processMessage before any other SSL/TLS processing.
+ deferredCerts = peerCerts;
+ } else {
+ // We're not doing stapling, so perform the check right now
+ checkServerCerts(peerCerts);
+ }
+ }
- if (tm instanceof X509ExtendedTrustManager) {
- if (conn != null) {
- ((X509ExtendedTrustManager)tm).checkServerTrusted(
- peerCerts.clone(),
- keyExchangeString,
- conn);
- } else {
- ((X509ExtendedTrustManager)tm).checkServerTrusted(
- peerCerts.clone(),
- keyExchangeString,
- engine);
- }
- } else {
- // Unlikely to happen, because we have wrapped the old
- // X509TrustManager with the new X509ExtendedTrustManager.
- throw new CertificateException(
- "Improper X509TrustManager implementation");
- }
- } catch (CertificateException e) {
- // This will throw an exception, so include the original error.
- fatalSE(Alerts.alert_certificate_unknown, e);
+ /**
+ * If certificate status stapling has been enabled, the server will send
+ * one or more status messages to the client.
+ *
+ * @param mesg a {@code CertificateStatus} object built from the data
+ * sent by the server.
+ *
+ * @throws IOException if any parsing errors occur.
+ */
+ private void certificateStatus(CertificateStatus mesg) throws IOException {
+ if (debug != null && Debug.isOn("handshake")) {
+ mesg.print(System.out);
}
- session.setPeerCertificates(peerCerts);
+
+ // Perform the certificate check using the deferred certificates
+ // and responses that we have obtained.
+ session.setStatusResponses(mesg.getResponses());
+ checkServerCerts(deferredCerts);
}
/*
@@ -1700,4 +1742,88 @@
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;
+ }
}
+
--- a/jdk/src/java.base/share/classes/sun/security/ssl/ExtensionType.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ExtensionType.java Wed Aug 05 12:19:38 2015 -0700
@@ -44,7 +44,7 @@
}
static List<ExtensionType> knownExtensions =
- new ArrayList<ExtensionType>(13);
+ new ArrayList<ExtensionType>(14);
static ExtensionType get(int id) {
for (ExtensionType ext : knownExtensions) {
@@ -97,6 +97,10 @@
final static ExtensionType EXT_SIGNATURE_ALGORITHMS =
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
final static ExtensionType EXT_RENEGOTIATION_INFO =
e(0xff01, "renegotiation_info"); // IANA registry value: 65281
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeMessage.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeMessage.java Wed Aug 05 12:19:38 2015 -0700
@@ -49,6 +49,7 @@
import sun.security.ssl.CipherSuite.*;
import static sun.security.ssl.CipherSuite.PRF.*;
import sun.security.util.KeyUtil;
+import sun.security.provider.certpath.OCSPResponse;
/**
* Many data structures are involved in the handshake messages. These
@@ -393,6 +394,24 @@
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
int messageType() { return ht_client_hello; }
@@ -636,6 +655,240 @@
}
/*
+ * 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
*
* The cipher suite selected, when combined with the certificate exchanged,
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeStateManager.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeStateManager.java Wed Aug 05 12:19:38 2015 -0700
@@ -25,12 +25,12 @@
package sun.security.ssl;
+import java.util.Collections;
+import java.util.List;
import java.util.LinkedList;
import java.util.HashMap;
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.HandshakeStateManager.HandshakeState.*;
@@ -311,7 +311,7 @@
HS_SERVER_CHANGE_CIPHER_SPEC(
"server change_cipher_spec",
HandshakeMessage.ht_not_applicable),
- HS_SERVER_FINISHDE(
+ HS_SERVER_FINISHED(
"server finished",
HandshakeMessage.ht_finished);
@@ -343,7 +343,8 @@
return upcomingStates.isEmpty();
}
- void check(byte handshakeType) throws SSLProtocolException {
+ List<Byte> check(byte handshakeType) throws SSLProtocolException {
+ List<Byte> ignoredOptional = new LinkedList<>();
String exceptionMsg =
"Handshake message sequence violation, " + handshakeType;
@@ -362,27 +363,28 @@
}
// 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.
if (handshakeType == HandshakeMessage.ht_hello_request) {
- return;
+ return Collections.emptyList();
}
for (HandshakeState handshakeState : upcomingStates) {
if (handshakeState.handshakeType == handshakeType) {
// It's the expected next handshake type.
- return;
+ return ignoredOptional;
}
if (handshakeState.isOptional) {
+ ignoredOptional.add(handshakeState.handshakeType);
continue;
} else {
for (HandshakeState alternative : alternatives) {
if (alternative.handshakeType == handshakeType) {
- return;
+ return ignoredOptional;
}
if (alternative.isOptional) {
@@ -541,7 +543,7 @@
// (Server Finished Flight)
// HS_NEW_SESSION_TICKET
// --> HS_SERVER_CHANGE_CIPHER_SPEC
- // --> HS_SERVER_FINISHDE
+ // --> HS_SERVER_FINISHED
// (Client Finished Flight)
// --> HS_CLIENT_CHANGE_CIPHER_SPEC
// --> HS_CLEINT_FINISHED
@@ -587,7 +589,7 @@
// Mandatory server ChangeCipherSpec and Finished messages
upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
- upcomingStates.add(HS_SERVER_FINISHDE);
+ upcomingStates.add(HS_SERVER_FINISHED);
// Mandatory client ChangeCipherSpec and Finished messages
upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
@@ -598,15 +600,11 @@
// boolean hasSupplementalDataExt =
// (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.
//
// boolean hasCertificateUrlExt =
- // (hes.get(HandshakeMessage.ht_certificate_url) != null);
+ // (hes.get(ExtensionType EXT_CLIENT_CERTIFICATE_URL)
+ // != null);
// Not support SupplementalData extension yet.
//
@@ -625,12 +623,11 @@
upcomingStates.add(HS_SERVER_CERTIFICATE);
}
- // Not support CertificateStatus extension yet.
- //
- // // Optional CertificateStatus message
- // if (hasCertificateStatusExt) {
- // upcomingStates.add(HS_CERTIFICATE_STATUS);
- // }
+ // Optional CertificateStatus message
+ if (hes.get(ExtensionType.EXT_STATUS_REQUEST) != null ||
+ hes.get(ExtensionType.EXT_STATUS_REQUEST_V2) != null) {
+ upcomingStates.add(HS_CERTIFICATE_STATUS);
+ }
// Need ServerKeyExchange message or not?
if ((keyExchange == K_RSA_EXPORT) ||
@@ -690,7 +687,7 @@
// Mandatory server ChangeCipherSpec and Finished messages
upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
- upcomingStates.add(HS_SERVER_FINISHDE);
+ upcomingStates.add(HS_SERVER_FINISHED);
}
break;
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HelloExtensions.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HelloExtensions.java Wed Aug 05 12:19:38 2015 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -87,6 +87,10 @@
extension = new RenegotiationInfoExtension(s, extlen);
} else if (extType == ExtensionType.EXT_MAX_FRAGMENT_LENGTH) {
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 {
extension = new UnknownExtension(s, extlen, extType);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/OCSPStatusRequest.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java Wed Aug 05 12:19:38 2015 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -65,6 +65,8 @@
// DTLS cookie exchange manager
private HelloCookieManager helloCookieManager;
+ private StatusResponseManager statusResponseManager;
+
SSLContextImpl() {
ephemeralKeyManager = new EphemeralKeyManager();
clientCache = new SSLSessionContextImpl();
@@ -88,6 +90,7 @@
}
}
trustManager = chooseTrustManager(tm);
+ statusResponseManager = new StatusResponseManager();
if (sr == null) {
secureRandom = JsseJce.getSecureRandom();
@@ -256,6 +259,10 @@
"Cookie exchange applies to DTLS only");
}
+ StatusResponseManager getStatusResponseManager() {
+ return statusResponseManager;
+ }
+
abstract SSLParameters getDefaultServerSSLParams();
abstract SSLParameters getDefaultClientSSLParams();
abstract SSLParameters getSupportedSSLParams();
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java Wed Aug 05 12:19:38 2015 -0700
@@ -108,6 +108,7 @@
private String[] localSupportedSignAlgs;
private String[] peerSupportedSignAlgs;
private List<SNIServerName> requestedServerNames;
+ private List<byte[]> statusResponses;
private int negotiatedMaxFragLen;
private int maximumPacketSize;
@@ -180,6 +181,7 @@
localSupportedSignAlgs =
SignatureAndHashAlgorithm.getAlgorithmNames(algorithms);
negotiatedMaxFragLen = -1;
+ statusResponses = null;
if (debug != null && Debug.isOn("session")) {
System.out.println("%% Initialized: " + this);
@@ -226,6 +228,19 @@
}
/**
+ * 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.
*/
void setPeerPrincipal(Principal principal) {
@@ -532,6 +547,30 @@
}
/**
+ * 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
* defining the session.
*
--- a/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java Wed Aug 05 12:19:38 2015 -0700
@@ -28,6 +28,7 @@
import java.io.*;
import java.util.*;
+import java.util.concurrent.TimeUnit;
import java.security.*;
import java.security.cert.*;
import java.security.interfaces.*;
@@ -36,9 +37,9 @@
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
-
import javax.net.ssl.*;
+import sun.security.action.GetLongAction;
import sun.security.util.KeyUtil;
import sun.security.util.LegacyAlgorithmConstraints;
import sun.security.action.GetPropertyAction;
@@ -57,11 +58,16 @@
*/
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?
private ClientAuthType doClientAuth;
// our authentication info
private X509Certificate[] certs;
+ private Map<X509Certificate, byte[]> responseMap;
private PrivateKey privateKey;
private Object serviceCreds;
@@ -112,6 +118,13 @@
LegacyAlgorithmConstraints.PROPERTY_TLS_LEGACY_ALGS,
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 {
String property = AccessController.doPrivileged(
new GetPropertyAction("jdk.tls.ephemeralDHKeySize"));
@@ -159,6 +172,11 @@
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
clientVerifyData, serverVerifyData);
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 @@
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
clientVerifyData, serverVerifyData, isDTLS);
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 @@
}
}
+ // 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
* from the ClientHello. Update the (pending) cipher spec as we do
@@ -825,6 +858,69 @@
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")) {
m1.print(System.out);
System.out.println("Cipher suite: " + session.getSuite());
@@ -886,6 +982,32 @@
}
}
+ /**
+ * 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.
*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/StatusRequest.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/StatusRequestType.java Wed Aug 05 12:19:38 2015 -0700
@@ -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");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/UnknownStatusRequest.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java Wed Aug 05 12:19:38 2015 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -184,6 +184,7 @@
Validator v = checkTrustedInit(chain, authType, isClient);
AlgorithmConstraints constraints = null;
+ List<byte[]> responseList = Collections.emptyList();
if ((socket != null) && socket.isConnected() &&
(socket instanceof SSLSocket)) {
@@ -195,7 +196,7 @@
// check endpoint identity
String identityAlg = sslSocket.getSSLParameters().
- getEndpointIdentificationAlgorithm();
+ getEndpointIdentificationAlgorithm();
if (identityAlg != null && identityAlg.length() != 0) {
checkIdentity(session, chain[0], identityAlg, isClient,
getRequestedServerNames(socket));
@@ -204,7 +205,7 @@
// create the algorithm constraints
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
- if (protocolVersion.useTLS12PlusSpec()) {
+ if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession =
(ExtendedSSLSession)session;
@@ -220,13 +221,21 @@
} else {
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;
if (isClient) {
- trustedChain = validate(v, chain, constraints, null);
+ trustedChain = validate(v, chain, Collections.emptyList(),
+ constraints, null);
} else {
- trustedChain = validate(v, chain, constraints, authType);
+ trustedChain = validate(v, chain, responseList, constraints,
+ authType);
}
if (debug != null && Debug.isOn("trustmanager")) {
System.out.println("Found trusted certificate:");
@@ -239,6 +248,7 @@
Validator v = checkTrustedInit(chain, authType, isClient);
AlgorithmConstraints constraints = null;
+ List<byte[]> responseList = Collections.emptyList();
if (engine != null) {
SSLSession session = engine.getHandshakeSession();
if (session == null) {
@@ -247,7 +257,7 @@
// check endpoint identity
String identityAlg = engine.getSSLParameters().
- getEndpointIdentificationAlgorithm();
+ getEndpointIdentificationAlgorithm();
if (identityAlg != null && identityAlg.length() != 0) {
checkIdentity(session, chain[0], identityAlg, isClient,
getRequestedServerNames(engine));
@@ -256,7 +266,7 @@
// create the algorithm constraints
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
- if (protocolVersion.useTLS12PlusSpec()) {
+ if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession =
(ExtendedSSLSession)session;
@@ -272,13 +282,21 @@
} else {
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;
if (isClient) {
- trustedChain = validate(v, chain, constraints, null);
+ trustedChain = validate(v, chain, Collections.emptyList(),
+ constraints, null);
} else {
- trustedChain = validate(v, chain, constraints, authType);
+ trustedChain = validate(v, chain, responseList, constraints,
+ authType);
}
if (debug != null && Debug.isOn("trustmanager")) {
System.out.println("Found trusted certificate:");
@@ -317,11 +335,12 @@
}
private static X509Certificate[] validate(Validator v,
- X509Certificate[] chain, AlgorithmConstraints constraints,
- String authType) throws CertificateException {
+ X509Certificate[] chain, List<byte[]> responseList,
+ AlgorithmConstraints constraints, String authType)
+ throws CertificateException {
Object o = JsseJce.beginFipsProvider();
try {
- return v.validate(chain, null, constraints, authType);
+ return v.validate(chain, null, responseList, constraints, authType);
} finally {
JsseJce.endFipsProvider(o);
}
--- a/jdk/src/java.base/share/classes/sun/security/validator/PKIXValidator.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/validator/PKIXValidator.java Wed Aug 05 12:19:38 2015 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -186,6 +186,7 @@
@Override
X509Certificate[] engineValidate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts,
+ List<byte[]> responseList,
AlgorithmConstraints constraints,
Object parameter) throws CertificateException {
if ((chain == null) || (chain.length == 0)) {
@@ -193,7 +194,7 @@
("null or zero-length certificate chain");
}
- // add new algorithm constraints checker
+ // add new algorithm constraints checker
PKIXBuilderParameters pkixParameters =
(PKIXBuilderParameters) parameterTemplate.clone();
AlgorithmChecker algorithmChecker = null;
@@ -202,6 +203,11 @@
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
// trust anchor
X500Principal prevIssuer = null;
@@ -369,4 +375,70 @@
("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.
+ }
+ }
+ }
}
--- a/jdk/src/java.base/share/classes/sun/security/validator/SimpleValidator.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/validator/SimpleValidator.java Wed Aug 05 12:19:38 2015 -0700
@@ -122,6 +122,7 @@
@Override
X509Certificate[] engineValidate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts,
+ List<byte[]> responseList,
AlgorithmConstraints constraints,
Object parameter) throws CertificateException {
if ((chain == null) || (chain.length == 0)) {
--- a/jdk/src/java.base/share/classes/sun/security/validator/Validator.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/validator/Validator.java Wed Aug 05 12:19:38 2015 -0700
@@ -235,7 +235,8 @@
public final X509Certificate[] validate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts, Object parameter)
throws CertificateException {
- return validate(chain, otherCerts, null, parameter);
+ return validate(chain, otherCerts, Collections.emptyList(), null,
+ parameter);
}
/**
@@ -244,6 +245,13 @@
* @param chain the target certificate chain
* @param otherCerts a Collection of additional X509Certificates that
* 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
* processing
* @param parameter an additional parameter with variant specific meaning.
@@ -257,9 +265,11 @@
*/
public final X509Certificate[] validate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts,
+ List<byte[]> responseList,
AlgorithmConstraints constraints,
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
if (chain.length > 1) {
@@ -280,6 +290,7 @@
abstract X509Certificate[] engineValidate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts,
+ List<byte[]> responseList,
AlgorithmConstraints constraints,
Object parameter) throws CertificateException;
--- a/jdk/src/java.base/share/classes/sun/security/x509/PKIXExtensions.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/x509/PKIXExtensions.java Wed Aug 05 12:19:38 2015 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -77,6 +77,11 @@
private static final int[] OCSPNoCheck_data = { 1, 3, 6, 1, 5, 5, 7,
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.
*/
@@ -104,18 +109,20 @@
public static final ObjectIdentifier CertificatePolicies_Id;
/**
- * Lists pairs of objectidentifiers of policies considered equivalent by the
- * issuing CA to the subject CA.
+ * Lists pairs of object identifiers of policies considered equivalent by
+ * the issuing CA to the subject CA.
*/
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;
/**
- * 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;
@@ -224,6 +231,12 @@
*/
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 {
AuthorityKey_Id = ObjectIdentifier.newInternal(AuthorityKey_data);
SubjectKey_Id = ObjectIdentifier.newInternal(SubjectKey_data);
@@ -266,5 +279,6 @@
ObjectIdentifier.newInternal(SubjectInfoAccess_data);
FreshestCRL_Id = ObjectIdentifier.newInternal(FreshestCRL_data);
OCSPNoCheck_Id = ObjectIdentifier.newInternal(OCSPNoCheck_data);
+ OCSPNonce_Id = ObjectIdentifier.newInternal(OCSPNonce_data);
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/security/testlibrary/CertificateBuilder.java Wed Aug 05 12:19:38 2015 -0700
@@ -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();
+ }
+ }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/security/testlibrary/SimpleOCSPServer.java Wed Aug 05 12:19:38 2015 -0700
@@ -0,0 +1,1540 @@
+/*
+ * 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.net.*;
+import java.security.*;
+import java.security.cert.CRLReason;
+import java.security.cert.X509Certificate;
+import java.security.cert.Extension;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateEncodingException;
+import java.security.Signature;
+import java.util.*;
+import java.util.concurrent.*;
+import java.text.SimpleDateFormat;
+import java.math.BigInteger;
+
+import sun.security.x509.*;
+import sun.security.x509.PKIXExtensions;
+import sun.security.provider.certpath.ResponderId;
+import sun.security.provider.certpath.CertId;
+import sun.security.provider.certpath.OCSPResponse;
+import sun.security.provider.certpath.OCSPResponse.ResponseStatus;
+import sun.security.util.Debug;
+import sun.security.util.DerInputStream;
+import sun.security.util.DerOutputStream;
+import sun.security.util.DerValue;
+import sun.security.util.ObjectIdentifier;
+
+
+/**
+ * This is a simple OCSP server designed to listen and respond to incoming
+ * requests.
+ */
+public class SimpleOCSPServer {
+ private final Debug debug = Debug.getInstance("oserv");
+ private static final ObjectIdentifier OCSP_BASIC_RESPONSE_OID =
+ ObjectIdentifier.newInternal(
+ new int[] { 1, 3, 6, 1, 5, 5, 7, 48, 1, 1});
+ private static final SimpleDateFormat utcDateFmt =
+ new SimpleDateFormat("MMM dd yyyy, HH:mm:ss z");
+
+ // CertStatus values
+ public static enum CertStatus {
+ CERT_STATUS_GOOD,
+ CERT_STATUS_REVOKED,
+ CERT_STATUS_UNKNOWN,
+ }
+
+ // Fields used for the networking portion of the responder
+ private ServerSocket servSocket;
+ private InetAddress listenAddress;
+ private int listenPort;
+
+ // Keystore information (certs, keys, etc.)
+ private KeyStore keystore;
+ private X509Certificate issuerCert;
+ private X509Certificate signerCert;
+ private PrivateKey signerKey;
+
+ // Fields used for the operational portions of the server
+ private boolean logEnabled = false;
+ private ExecutorService threadPool;
+ private volatile boolean started = false;
+ private volatile boolean receivedShutdown = false;
+ private long delayMsec = 0;
+
+ // Fields used in the generation of responses
+ private long nextUpdateInterval = -1;
+ private Date nextUpdate = null;
+ private ResponderId respId;
+ private AlgorithmId sigAlgId;
+ private Map<CertId, CertStatusInfo> statusDb =
+ Collections.synchronizedMap(new HashMap<>());
+
+ /**
+ * Construct a SimpleOCSPServer using keystore, password, and alias
+ * parameters.
+ *
+ * @param ks the keystore to be used
+ * @param password the password to access key material in the keystore
+ * @param issuerAlias the alias of the issuer certificate
+ * @param signerAlias the alias of the signer certificate and key. A
+ * value of {@code null} means that the {@code issuerAlias} will be used
+ * to look up the signer key.
+ *
+ * @throws GeneralSecurityException if there are problems accessing the
+ * keystore or finding objects within the keystore.
+ * @throws IOException if a {@code ResponderId} cannot be generated from
+ * the signer certificate.
+ */
+ public SimpleOCSPServer(KeyStore ks, String password, String issuerAlias,
+ String signerAlias) throws GeneralSecurityException, IOException {
+ this(null, 0, ks, password, issuerAlias, signerAlias);
+ }
+
+ /**
+ * Construct a SimpleOCSPServer using specific network parameters,
+ * keystore, password, and alias.
+ *
+ * @param addr the address to bind the server to. A value of {@code null}
+ * means the server will bind to all interfaces.
+ * @param port the port to listen on. A value of {@code 0} will mean that
+ * the server will randomly pick an open ephemeral port to bind to.
+ * @param ks the keystore to be used
+ * @param password the password to access key material in the keystore
+ * @param issuerAlias the alias of the issuer certificate
+ * @param signerAlias the alias of the signer certificate and key. A
+ * value of {@code null} means that the {@code issuerAlias} will be used
+ * to look up the signer key.
+ *
+ * @throws GeneralSecurityException if there are problems accessing the
+ * keystore or finding objects within the keystore.
+ * @throws IOException if a {@code ResponderId} cannot be generated from
+ * the signer certificate.
+ */
+ public SimpleOCSPServer(InetAddress addr, int port, KeyStore ks,
+ String password, String issuerAlias, String signerAlias)
+ throws GeneralSecurityException, IOException {
+ Objects.requireNonNull(ks, "Null keystore provided");
+ Objects.requireNonNull(issuerAlias, "Null issuerName provided");
+
+ utcDateFmt.setTimeZone(TimeZone.getTimeZone("GMT"));
+
+ keystore = ks;
+ issuerCert = (X509Certificate)ks.getCertificate(issuerAlias);
+ if (issuerCert == null) {
+ throw new IllegalArgumentException("Certificate for alias " +
+ issuerAlias + " not found");
+ }
+
+ if (signerAlias != null) {
+ signerCert = (X509Certificate)ks.getCertificate(signerAlias);
+ if (signerCert == null) {
+ throw new IllegalArgumentException("Certificate for alias " +
+ signerAlias + " not found");
+ }
+ signerKey = (PrivateKey)ks.getKey(signerAlias,
+ password.toCharArray());
+ if (signerKey == null) {
+ throw new IllegalArgumentException("PrivateKey for alias " +
+ signerAlias + " not found");
+ }
+ } else {
+ signerCert = issuerCert;
+ signerKey = (PrivateKey)ks.getKey(issuerAlias,
+ password.toCharArray());
+ if (signerKey == null) {
+ throw new IllegalArgumentException("PrivateKey for alias " +
+ issuerAlias + " not found");
+ }
+ }
+
+ sigAlgId = AlgorithmId.get("Sha256withRSA");
+ respId = new ResponderId(signerCert.getSubjectX500Principal());
+ listenAddress = addr;
+ listenPort = port;
+ }
+
+ /**
+ * Start the server. The server will bind to the specified network
+ * address and begin listening for incoming connections.
+ *
+ * @throws IOException if any number of things go wonky.
+ */
+ public synchronized void start() throws IOException {
+ // You cannot start the server twice.
+ if (started) {
+ log("Server has already been started");
+ return;
+ } else {
+ started = true;
+ }
+
+ // Create and start the thread pool
+ threadPool = Executors.newFixedThreadPool(32, new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = Executors.defaultThreadFactory().newThread(r);
+ t.setDaemon(true);
+ return t;
+ }
+ });
+
+ threadPool.submit(new Runnable() {
+ @Override
+ public void run() {
+ try (ServerSocket sSock = new ServerSocket()) {
+ servSocket = sSock;
+ servSocket.setReuseAddress(true);
+ servSocket.setSoTimeout(500);
+ servSocket.bind(new InetSocketAddress(listenAddress,
+ listenPort), 128);
+ log("Listening on " + servSocket.getLocalSocketAddress());
+
+ // Update the listenPort with the new port number. If
+ // the server is restarted, it will bind to the same
+ // port rather than picking a new one.
+ listenPort = servSocket.getLocalPort();
+
+ // Main dispatch loop
+ while (!receivedShutdown) {
+ try {
+ Socket newConnection = servSocket.accept();
+ threadPool.submit(new OcspHandler(newConnection));
+ } catch (SocketTimeoutException timeout) {
+ // Nothing to do here. If receivedShutdown
+ // has changed to true then the loop will
+ // exit on its own.
+ } catch (IOException ioe) {
+ // Something bad happened, log and force a shutdown
+ log("Unexpected Exception: " + ioe);
+ stop();
+ }
+ }
+
+ log("Shutting down...");
+ threadPool.shutdown();
+ } catch (IOException ioe) {
+ err(ioe);
+ }
+
+ // Reset state variables so the server can be restarted
+ receivedShutdown = false;
+ started = false;
+ }
+ });
+ }
+
+ /**
+ * Stop the OCSP server.
+ */
+ public synchronized void stop() {
+ if (started) {
+ receivedShutdown = true;
+ log("Received shutdown notification");
+ }
+ }
+
+ /**
+ * Print {@code SimpleOCSPServer} operating parameters.
+ *
+ * @return the {@code SimpleOCSPServer} operating parameters in
+ * {@code String} form.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("OCSP Server:\n");
+ sb.append("----------------------------------------------\n");
+ sb.append("issuer: ").append(issuerCert.getSubjectX500Principal()).
+ append("\n");
+ sb.append("signer: ").append(signerCert.getSubjectX500Principal()).
+ append("\n");
+ sb.append("ResponderId: ").append(respId).append("\n");
+ sb.append("----------------------------------------------");
+
+ return sb.toString();
+ }
+
+ /**
+ * Helpful debug routine to hex dump byte arrays.
+ *
+ * @param data the array of bytes to dump to stdout.
+ *
+ * @return the hexdump of the byte array
+ */
+ private static String dumpHexBytes(byte[] data) {
+ return dumpHexBytes(data, 16, "\n", " ");
+ }
+
+ /**
+ *
+ * @param data the array of bytes to dump to stdout.
+ * @param itemsPerLine the number of bytes to display per line
+ * if the {@code lineDelim} character is blank then all bytes will be
+ * printed on a single line.
+ * @param lineDelim the delimiter between lines
+ * @param itemDelim the delimiter between bytes
+ *
+ * @return The hexdump of the byte array
+ */
+ private static String dumpHexBytes(byte[] data, int itemsPerLine,
+ String lineDelim, String itemDelim) {
+ StringBuilder sb = new StringBuilder();
+ if (data != null) {
+ for (int i = 0; i < data.length; i++) {
+ if (i % itemsPerLine == 0 && i != 0) {
+ sb.append(lineDelim);
+ }
+ sb.append(String.format("%02X", data[i])).append(itemDelim);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Enable or disable the logging feature.
+ *
+ * @param enable {@code true} to enable logging, {@code false} to
+ * disable it. The setting must be activated before the server calls
+ * its start method. Any calls after that have no effect.
+ */
+ public void enableLog(boolean enable) {
+ if (!started) {
+ logEnabled = enable;
+ }
+ }
+
+ /**
+ * Sets the nextUpdate interval. Intervals will be calculated relative
+ * to the server startup time. When first set, the nextUpdate date is
+ * calculated based on the current time plus the interval. After that,
+ * calls to getNextUpdate() will return this date if it is still
+ * later than current time. If not, the Date will be updated to the
+ * next interval that is later than current time. This value must be set
+ * before the server has had its start method called. Calls made after
+ * the server has been started have no effect.
+ *
+ * @param interval the recurring time interval in seconds used to
+ * calculate nextUpdate times. A value less than or equal to 0 will
+ * disable the nextUpdate feature.
+ */
+ public synchronized void setNextUpdateInterval(long interval) {
+ if (!started) {
+ if (interval <= 0) {
+ nextUpdateInterval = -1;
+ nextUpdate = null;
+ log("nexUpdate support has been disabled");
+ } else {
+ nextUpdateInterval = interval * 1000;
+ nextUpdate = new Date(System.currentTimeMillis() +
+ nextUpdateInterval);
+ log("nextUpdate set to " + nextUpdate);
+ }
+ }
+ }
+
+ /**
+ * Return the nextUpdate {@code Date} object for this server. If the
+ * nextUpdate date has already passed, set a new nextUpdate based on
+ * the nextUpdate interval and return that date.
+ *
+ * @return a {@code Date} object set to the nextUpdate field for OCSP
+ * responses.
+ */
+ private synchronized Date getNextUpdate() {
+ if (nextUpdate != null && nextUpdate.before(new Date())) {
+ long nuEpochTime = nextUpdate.getTime();
+ long currentTime = System.currentTimeMillis();
+
+ // Keep adding nextUpdate intervals until you reach a date
+ // that is later than current time.
+ while (currentTime >= nuEpochTime) {
+ nuEpochTime += nextUpdateInterval;
+ }
+
+ // Set the nextUpdate for future threads
+ nextUpdate = new Date(nuEpochTime);
+ log("nextUpdate updated to new value: " + nextUpdate);
+ }
+ return nextUpdate;
+ }
+
+ /**
+ * Add entries into the responder's status database.
+ *
+ * @param newEntries a map of {@code CertStatusInfo} objects, keyed on
+ * their serial number (as a {@code BigInteger}). All serial numbers
+ * are assumed to have come from this responder's issuer certificate.
+ *
+ * @throws IOException if a CertId cannot be generated.
+ */
+ public void updateStatusDb(Map<BigInteger, CertStatusInfo> newEntries)
+ throws IOException {
+ if (newEntries != null) {
+ for (BigInteger serial : newEntries.keySet()) {
+ CertStatusInfo info = newEntries.get(serial);
+ if (info != null) {
+ CertId cid = new CertId(issuerCert,
+ new SerialNumber(serial));
+ statusDb.put(cid, info);
+ log("Added entry for serial " + serial + "(" +
+ info.getType() + ")");
+ }
+ }
+ }
+ }
+
+ /**
+ * Check the status database for revocation information one one or more
+ * certificates.
+ *
+ * @param reqList the list of {@code LocalSingleRequest} objects taken
+ * from the incoming OCSP request.
+ *
+ * @return a {@code Map} of {@code CertStatusInfo} objects keyed by their
+ * {@code CertId} values, for each single request passed in. Those
+ * CertIds not found in the statusDb will have returned List members with
+ * a status of UNKNOWN.
+ */
+ private Map<CertId, CertStatusInfo> checkStatusDb(
+ List<LocalOcspRequest.LocalSingleRequest> reqList) {
+ // TODO figure out what, if anything to do with request extensions
+ Map<CertId, CertStatusInfo> returnMap = new HashMap<>();
+
+ for (LocalOcspRequest.LocalSingleRequest req : reqList) {
+ CertId cid = req.getCertId();
+ CertStatusInfo info = statusDb.get(cid);
+ if (info != null) {
+ log("Status for SN " + cid.getSerialNumber() + ": " +
+ info.getType());
+ returnMap.put(cid, info);
+ } else {
+ log("Status for SN " + cid.getSerialNumber() +
+ " not found, using CERT_STATUS_UNKNOWN");
+ returnMap.put(cid,
+ new CertStatusInfo(CertStatus.CERT_STATUS_UNKNOWN));
+ }
+ }
+
+ return Collections.unmodifiableMap(returnMap);
+ }
+
+ /**
+ * Set the digital signature algorithm used to sign OCSP responses.
+ *
+ * @param algName The algorithm name
+ *
+ * @throws NoSuchAlgorithmException if the algorithm name is invalid.
+ */
+ public void setSignatureAlgorithm(String algName)
+ throws NoSuchAlgorithmException {
+ if (!started) {
+ sigAlgId = AlgorithmId.get(algName);
+ }
+ }
+
+ /**
+ * Get the port the OCSP server is running on.
+ *
+ * @return the port that the OCSP server is running on, or -1 if the
+ * server has not yet been bound to a port.
+ */
+ public int getPort() {
+ if (servSocket != null && started) {
+ InetSocketAddress inetSock =
+ (InetSocketAddress)servSocket.getLocalSocketAddress();
+ return inetSock.getPort();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Set a delay between the reception of the request and production of
+ * the response.
+ *
+ * @param delayMillis the number of milliseconds to wait before acting
+ * on the incoming request.
+ */
+ public void setDelay(long delayMillis) {
+ if (!started) {
+ delayMsec = delayMillis > 0 ? delayMillis : 0;
+ if (delayMsec > 0) {
+ log("OCSP latency set to " + delayMsec + " milliseconds.");
+ } else {
+ log("OCSP latency disabled");
+ }
+ }
+ }
+
+ /**
+ * Log a message to stdout.
+ *
+ * @param message the message to log
+ */
+ private synchronized void log(String message) {
+ if (logEnabled || debug != null) {
+ System.out.println("[" + Thread.currentThread().getName() + "]: " +
+ message);
+ }
+ }
+
+ /**
+ * Log an error message on the stderr stream.
+ *
+ * @param message the message to log
+ */
+ private static synchronized void err(String message) {
+ System.out.println("[" + Thread.currentThread().getName() + "]: " +
+ message);
+ }
+
+ /**
+ * Log exception information on the stderr stream.
+ *
+ * @param exc the exception to dump information about
+ */
+ private static synchronized void err(Throwable exc) {
+ System.out.print("[" + Thread.currentThread().getName() +
+ "]: Exception: ");
+ exc.printStackTrace(System.out);
+ }
+
+ /**
+ * The {@code CertStatusInfo} class defines an object used to return
+ * information from the internal status database. The data in this
+ * object may be used to construct OCSP responses.
+ */
+ public static class CertStatusInfo {
+ private CertStatus certStatusType;
+ private CRLReason reason;
+ private Date revocationTime;
+
+ /**
+ * Create a Certificate status object by providing the status only.
+ * If the status is {@code REVOKED} then current time is assumed
+ * for the revocation time.
+ *
+ * @param statType the status for this entry.
+ */
+ public CertStatusInfo(CertStatus statType) {
+ this(statType, null, null);
+ }
+
+ /**
+ * Create a CertStatusInfo providing both type and revocation date
+ * (if applicable).
+ *
+ * @param statType the status for this entry.
+ * @param revDate if applicable, the date that revocation took place.
+ * A value of {@code null} indicates that current time should be used.
+ * If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},
+ * then the {@code revDate} parameter is ignored.
+ */
+ public CertStatusInfo(CertStatus statType, Date revDate) {
+ this(statType, revDate, null);
+ }
+
+ /**
+ * Create a CertStatusInfo providing type, revocation date
+ * (if applicable) and revocation reason.
+ *
+ * @param statType the status for this entry.
+ * @param revDate if applicable, the date that revocation took place.
+ * A value of {@code null} indicates that current time should be used.
+ * If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},
+ * then the {@code revDate} parameter is ignored.
+ * @param revReason the reason the certificate was revoked. A value of
+ * {@code null} means that no reason was provided.
+ */
+ public CertStatusInfo(CertStatus statType, Date revDate,
+ CRLReason revReason) {
+ Objects.requireNonNull(statType, "Cert Status must be non-null");
+ certStatusType = statType;
+ switch (statType) {
+ case CERT_STATUS_GOOD:
+ case CERT_STATUS_UNKNOWN:
+ revocationTime = null;
+ break;
+ case CERT_STATUS_REVOKED:
+ revocationTime = revDate != null ? (Date)revDate.clone() :
+ new Date();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown status type: " +
+ statType);
+ }
+ }
+
+ /**
+ * Get the cert status type
+ *
+ * @return the status applied to this object (e.g.
+ * {@code CERT_STATUS_GOOD}, {@code CERT_STATUS_UNKNOWN}, etc.)
+ */
+ public CertStatus getType() {
+ return certStatusType;
+ }
+
+ /**
+ * Get the revocation time (if applicable).
+ *
+ * @return the revocation time as a {@code Date} object, or
+ * {@code null} if not applicable (i.e. if the certificate hasn't been
+ * revoked).
+ */
+ public Date getRevocationTime() {
+ return (revocationTime != null ? (Date)revocationTime.clone() :
+ null);
+ }
+
+ /**
+ * Get the revocation reason.
+ *
+ * @return the revocation reason, or {@code null} if one was not
+ * provided.
+ */
+ public CRLReason getRevocationReason() {
+ return reason;
+ }
+ }
+
+ /**
+ * Runnable task that handles incoming OCSP Requests and returns
+ * responses.
+ */
+ private class OcspHandler implements Runnable {
+ private final Socket sock;
+ InetSocketAddress peerSockAddr;
+
+ /**
+ * Construct an {@code OcspHandler}.
+ *
+ * @param incomingSocket the socket the server created on accept()
+ */
+ private OcspHandler(Socket incomingSocket) {
+ sock = incomingSocket;
+ }
+
+ /**
+ * Run the OCSP Request parser and construct a response to be sent
+ * back to the client.
+ */
+ @Override
+ public void run() {
+ // If we have implemented a delay to simulate network latency
+ // wait out the delay here before any other processing.
+ try {
+ if (delayMsec > 0) {
+ Thread.sleep(delayMsec);
+ }
+ } catch (InterruptedException ie) {
+ // Just log the interrupted sleep
+ log("Delay of " + delayMsec + " milliseconds was interrupted");
+ }
+
+ try (Socket ocspSocket = sock;
+ InputStream in = ocspSocket.getInputStream();
+ OutputStream out = ocspSocket.getOutputStream()) {
+ peerSockAddr =
+ (InetSocketAddress)ocspSocket.getRemoteSocketAddress();
+ log("Received incoming connection from " + peerSockAddr);
+ String[] headerTokens = readLine(in).split(" ");
+ LocalOcspRequest ocspReq = null;
+ LocalOcspResponse ocspResp = null;
+ ResponseStatus respStat = ResponseStatus.INTERNAL_ERROR;
+ try {
+ if (headerTokens[0] != null) {
+ switch (headerTokens[0]) {
+ case "POST":
+ ocspReq = parseHttpOcspPost(in);
+ break;
+ case "GET":
+ // req = parseHttpOcspGet(in);
+ // TODO implement the GET parsing
+ throw new IOException("GET method unsupported");
+ default:
+ respStat = ResponseStatus.MALFORMED_REQUEST;
+ throw new IOException("Not a GET or POST");
+ }
+ } else {
+ respStat = ResponseStatus.MALFORMED_REQUEST;
+ throw new IOException("Unable to get HTTP method");
+ }
+
+ if (ocspReq != null) {
+ log(ocspReq.toString());
+ // Get responses for all CertIds in the request
+ Map<CertId, CertStatusInfo> statusMap =
+ checkStatusDb(ocspReq.getRequests());
+ if (statusMap.isEmpty()) {
+ respStat = ResponseStatus.UNAUTHORIZED;
+ } else {
+ ocspResp = new LocalOcspResponse(
+ ResponseStatus.SUCCESSFUL, statusMap,
+ ocspReq.getExtensions());
+ }
+ } else {
+ respStat = ResponseStatus.MALFORMED_REQUEST;
+ throw new IOException("Found null request");
+ }
+ } catch (IOException | RuntimeException exc) {
+ err(exc);
+ }
+ if (ocspResp == null) {
+ ocspResp = new LocalOcspResponse(respStat);
+ }
+ sendResponse(out, ocspResp);
+ } catch (IOException | CertificateException exc) {
+ err(exc);
+ }
+ }
+
+ /**
+ * Send an OCSP response on an {@code OutputStream}.
+ *
+ * @param out the {@code OutputStream} on which to send the response.
+ * @param resp the OCSP response to send.
+ *
+ * @throws IOException if an encoding error occurs.
+ */
+ public void sendResponse(OutputStream out, LocalOcspResponse resp)
+ throws IOException {
+ StringBuilder sb = new StringBuilder();
+
+ byte[] respBytes;
+ try {
+ respBytes = resp.getBytes();
+ } catch (RuntimeException re) {
+ err(re);
+ return;
+ }
+
+ sb.append("HTTP/1.0 200 OK\r\n");
+ sb.append("Content-Type: application/ocsp-response\r\n");
+ sb.append("Content-Length: ").append(respBytes.length);
+ sb.append("\r\n\r\n");
+
+ out.write(sb.toString().getBytes("UTF-8"));
+ out.write(respBytes);
+ log(resp.toString());
+ }
+
+ /**
+ * Parse the incoming HTTP POST of an OCSP Request.
+ *
+ * @param inStream the input stream from the socket bound to this
+ * {@code OcspHandler}.
+ *
+ * @return the OCSP Request as a {@code LocalOcspRequest}
+ *
+ * @throws IOException if there are network related issues or problems
+ * occur during parsing of the OCSP request.
+ * @throws CertificateException if one or more of the certificates in
+ * the OCSP request cannot be read/parsed.
+ */
+ private LocalOcspRequest parseHttpOcspPost(InputStream inStream)
+ throws IOException, CertificateException {
+ boolean endOfHeader = false;
+ boolean properContentType = false;
+ int length = -1;
+
+ while (!endOfHeader) {
+ String[] lineTokens = readLine(inStream).split(" ");
+ if (lineTokens[0].isEmpty()) {
+ endOfHeader = true;
+ } else if (lineTokens[0].equalsIgnoreCase("Content-Type:")) {
+ if (lineTokens[1] == null ||
+ !lineTokens[1].equals(
+ "application/ocsp-request")) {
+ log("Unknown Content-Type: " +
+ (lineTokens[1] != null ?
+ lineTokens[1] : "<NULL>"));
+ return null;
+ } else {
+ properContentType = true;
+ log("Content-Type = " + lineTokens[1]);
+ }
+ } else if (lineTokens[0].equalsIgnoreCase("Content-Length:")) {
+ if (lineTokens[1] != null) {
+ length = Integer.parseInt(lineTokens[1]);
+ log("Content-Length = " + length);
+ }
+ }
+ }
+
+ // Okay, make sure we got what we needed from the header, then
+ // read the remaining OCSP Request bytes
+ if (properContentType && length >= 0) {
+ byte[] ocspBytes = new byte[length];
+ inStream.read(ocspBytes);
+ return new LocalOcspRequest(ocspBytes);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Read a line of text that is CRLF-delimited.
+ *
+ * @param is the {@code InputStream} tied to the socket
+ * for this {@code OcspHandler}
+ *
+ * @return a {@code String} consisting of the line of text
+ * read from the stream with the CRLF stripped.
+ *
+ * @throws IOException if any I/O error occurs.
+ */
+ private String readLine(InputStream is) throws IOException {
+ PushbackInputStream pbis = new PushbackInputStream(is);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ boolean done = false;
+ while (!done) {
+ byte b = (byte)pbis.read();
+ if (b == '\r') {
+ byte bNext = (byte)pbis.read();
+ if (bNext == '\n' || bNext == -1) {
+ done = true;
+ } else {
+ pbis.unread(bNext);
+ bos.write(b);
+ }
+ } else if (b == -1) {
+ done = true;
+ } else {
+ bos.write(b);
+ }
+ }
+
+ return new String(bos.toByteArray(), "UTF-8");
+ }
+ }
+
+
+ /**
+ * Simple nested class to handle OCSP requests without making
+ * changes to sun.security.provider.certpath.OCSPRequest
+ */
+ public class LocalOcspRequest {
+
+ private byte[] nonce;
+ private byte[] signature = null;
+ private AlgorithmId algId = null;
+ private int version = 0;
+ private GeneralName requestorName = null;
+ private Map<String, Extension> extensions = Collections.emptyMap();
+ private final List<LocalSingleRequest> requestList = new ArrayList<>();
+ private final List<X509Certificate> certificates = new ArrayList<>();
+
+ /**
+ * Construct a {@code LocalOcspRequest} from its DER encoding.
+ *
+ * @param requestBytes the DER-encoded bytes
+ *
+ * @throws IOException if decoding errors occur
+ * @throws CertificateException if certificates are found in the
+ * OCSP request and they do not parse correctly.
+ */
+ private LocalOcspRequest(byte[] requestBytes) throws IOException,
+ CertificateException {
+ Objects.requireNonNull(requestBytes, "Received null input");
+
+ DerInputStream dis = new DerInputStream(requestBytes);
+
+ // Parse the top-level structure, it should have no more than
+ // two elements.
+ DerValue[] topStructs = dis.getSequence(2);
+ for (DerValue dv : topStructs) {
+ if (dv.tag == DerValue.tag_Sequence) {
+ parseTbsRequest(dv);
+ } else if (dv.isContextSpecific((byte)0)) {
+ parseSignature(dv);
+ } else {
+ throw new IOException("Unknown tag at top level: " +
+ dv.tag);
+ }
+ }
+ }
+
+ /**
+ * Parse the signature block from an OCSP request
+ *
+ * @param sigSequence a {@code DerValue} containing the signature
+ * block at the outer sequence datum.
+ *
+ * @throws IOException if any non-certificate-based parsing errors occur
+ * @throws CertificateException if certificates are found in the
+ * OCSP request and they do not parse correctly.
+ */
+ private void parseSignature(DerValue sigSequence)
+ throws IOException, CertificateException {
+ DerValue[] sigItems = sigSequence.data.getSequence(3);
+ if (sigItems.length != 3) {
+ throw new IOException("Invalid number of signature items: " +
+ "expected 3, got " + sigItems.length);
+ }
+
+ algId = AlgorithmId.parse(sigItems[0]);
+ signature = sigItems[1].getBitString();
+
+ if (sigItems[2].isContextSpecific((byte)0)) {
+ DerValue[] certDerItems = sigItems[2].data.getSequence(4);
+ int i = 0;
+ for (DerValue dv : certDerItems) {
+ X509Certificate xc = new X509CertImpl(dv);
+ certificates.add(xc);
+ }
+ } else {
+ throw new IOException("Invalid tag in signature block: " +
+ sigItems[2].tag);
+ }
+ }
+
+ /**
+ * Parse the to-be-signed request data
+ *
+ * @param tbsReqSeq a {@code DerValue} object containing the to-be-
+ * signed OCSP request at the outermost SEQUENCE tag.
+ * @throws IOException if any parsing errors occur
+ */
+ private void parseTbsRequest(DerValue tbsReqSeq) throws IOException {
+ while (tbsReqSeq.data.available() > 0) {
+ DerValue dv = tbsReqSeq.data.getDerValue();
+ if (dv.isContextSpecific((byte)0)) {
+ // The version was explicitly called out
+ version = dv.data.getInteger();
+ } else if (dv.isContextSpecific((byte)1)) {
+ // A GeneralName was provided
+ requestorName = new GeneralName(dv.data.getDerValue());
+ } else if (dv.isContextSpecific((byte)2)) {
+ // Parse the extensions
+ DerValue[] extItems = dv.data.getSequence(2);
+ extensions = parseExtensions(extItems);
+ } else if (dv.tag == DerValue.tag_Sequence) {
+ while (dv.data.available() > 0) {
+ requestList.add(new LocalSingleRequest(dv.data));
+ }
+ }
+ }
+ }
+
+ /**
+ * Parse a SEQUENCE of extensions. This routine is used both
+ * at the overall request level and down at the singleRequest layer.
+ *
+ * @param extDerItems an array of {@code DerValue} items, each one
+ * consisting of a DER-encoded extension.
+ *
+ * @return a {@code Map} of zero or more extensions,
+ * keyed by its object identifier in {@code String} form.
+ *
+ * @throws IOException if any parsing errors occur.
+ */
+ private Map<String, Extension> parseExtensions(DerValue[] extDerItems)
+ throws IOException {
+ Map<String, Extension> extMap = new HashMap<>();
+
+ if (extDerItems != null && extDerItems.length != 0) {
+ for (DerValue extDerVal : extDerItems) {
+ sun.security.x509.Extension ext =
+ new sun.security.x509.Extension(extDerVal);
+ extMap.put(ext.getId(), ext);
+ }
+ }
+
+ return extMap;
+ }
+
+ /**
+ * Return the list of single request objects in this OCSP request.
+ *
+ * @return an unmodifiable {@code List} of zero or more requests.
+ */
+ private List<LocalSingleRequest> getRequests() {
+ return Collections.unmodifiableList(requestList);
+ }
+
+ /**
+ * Return the list of X.509 Certificates in this OCSP request.
+ *
+ * @return an unmodifiable {@code List} of zero or more
+ * {@cpde X509Certificate} objects.
+ */
+ private List<X509Certificate> getCertificates() {
+ return Collections.unmodifiableList(certificates);
+ }
+
+ /**
+ * Return the map of OCSP request extensions.
+ *
+ * @return an unmodifiable {@code Map} of zero or more
+ * {@code Extension} objects, keyed by their object identifiers
+ * in {@code String} form.
+ */
+ private Map<String, Extension> getExtensions() {
+ return Collections.unmodifiableMap(extensions);
+ }
+
+ /**
+ * Display the {@code LocalOcspRequest} in human readable form.
+ *
+ * @return a {@code String} representation of the
+ * {@code LocalOcspRequest}
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(String.format("OCSP Request: Version %d (0x%X)",
+ version + 1, version)).append("\n");
+ if (requestorName != null) {
+ sb.append("Requestor Name: ").append(requestorName).
+ append("\n");
+ }
+
+ int requestCtr = 0;
+ for (LocalSingleRequest lsr : requestList) {
+ sb.append("Request [").append(requestCtr++).append("]\n");
+ sb.append(lsr).append("\n");
+ }
+ if (!extensions.isEmpty()) {
+ sb.append("Extensions (").append(extensions.size()).
+ append(")\n");
+ for (Extension ext : extensions.values()) {
+ sb.append("\t").append(ext).append("\n");
+ }
+ }
+ if (signature != null) {
+ sb.append("Signature: ").append(algId).append("\n");
+ sb.append(dumpHexBytes(signature)).append("\n");
+ int certCtr = 0;
+ for (X509Certificate cert : certificates) {
+ sb.append("Certificate [").append(certCtr++).append("]").
+ append("\n");
+ sb.append("\tSubject: ");
+ sb.append(cert.getSubjectX500Principal()).append("\n");
+ sb.append("\tIssuer: ");
+ sb.append(cert.getIssuerX500Principal()).append("\n");
+ sb.append("\tSerial: ").append(cert.getSerialNumber());
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Inner class designed to handle the decoding/representation of
+ * single requests within a {@code LocalOcspRequest} object.
+ */
+ public class LocalSingleRequest {
+ private final CertId cid;
+ private Map<String, Extension> extensions = Collections.emptyMap();
+
+ private LocalSingleRequest(DerInputStream dis)
+ throws IOException {
+ DerValue[] srItems = dis.getSequence(2);
+
+ // There should be 1, possibly 2 DerValue items
+ if (srItems.length == 1 || srItems.length == 2) {
+ // The first parsable item should be the mandatory CertId
+ cid = new CertId(srItems[0].data);
+ if (srItems.length == 2) {
+ if (srItems[1].isContextSpecific((byte)0)) {
+ DerValue[] extDerItems = srItems[1].data.getSequence(2);
+ extensions = parseExtensions(extDerItems);
+ } else {
+ throw new IOException("Illegal tag in Request " +
+ "extensions: " + srItems[1].tag);
+ }
+ }
+ } else {
+ throw new IOException("Invalid number of items in " +
+ "Request (" + srItems.length + ")");
+ }
+ }
+
+ /**
+ * Get the {@code CertId} for this single request.
+ *
+ * @return the {@code CertId} for this single request.
+ */
+ private CertId getCertId() {
+ return cid;
+ }
+
+ /**
+ * Return the map of single request extensions.
+ *
+ * @return an unmodifiable {@code Map} of zero or more
+ * {@code Extension} objects, keyed by their object identifiers
+ * in {@code String} form.
+ */
+ private Map<String, Extension> getExtensions() {
+ return Collections.unmodifiableMap(extensions);
+ }
+
+ /**
+ * Display the {@code LocalSingleRequest} in human readable form.
+ *
+ * @return a {@code String} representation of the
+ * {@code LocalSingleRequest}
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("CertId, Algorithm = ");
+ sb.append(cid.getHashAlgorithm()).append("\n");
+ sb.append("\tIssuer Name Hash: ");
+ sb.append(dumpHexBytes(cid.getIssuerNameHash(), 256, "", ""));
+ sb.append("\n");
+ sb.append("\tIssuer Key Hash: ");
+ sb.append(dumpHexBytes(cid.getIssuerKeyHash(), 256, "", ""));
+ sb.append("\n");
+ sb.append("\tSerial Number: ").append(cid.getSerialNumber());
+ if (!extensions.isEmpty()) {
+ sb.append("Extensions (").append(extensions.size()).
+ append(")\n");
+ for (Extension ext : extensions.values()) {
+ sb.append("\t").append(ext).append("\n");
+ }
+ }
+
+ return sb.toString();
+ }
+ }
+ }
+
+ /**
+ * Simple nested class to handle OCSP requests without making
+ * changes to sun.security.provider.certpath.OCSPResponse
+ */
+ public class LocalOcspResponse {
+ private final int version = 0;
+ private final OCSPResponse.ResponseStatus responseStatus;
+ private final Map<CertId, CertStatusInfo> respItemMap;
+ private final Date producedAtDate;
+ private final List<LocalSingleResponse> singleResponseList =
+ new ArrayList<>();
+ private final Map<String, Extension> responseExtensions;
+ private byte[] signature;
+ private final List<X509Certificate> certificates;
+ private final byte[] encodedResponse;
+
+ /**
+ * Constructor for the generation of non-successful responses
+ *
+ * @param respStat the OCSP response status.
+ *
+ * @throws IOException if an error happens during encoding
+ * @throws NullPointerException if {@code respStat} is {@code null}
+ * or {@code respStat} is successful.
+ */
+ public LocalOcspResponse(OCSPResponse.ResponseStatus respStat)
+ throws IOException {
+ this(respStat, null, null);
+ }
+
+ /**
+ * Construct a response from a list of certificate
+ * status objects and extensions.
+ *
+ * @param respStat the status of the entire response
+ * @param itemMap a {@code Map} of {@code CertId} objects and their
+ * respective revocation statuses from the server's response DB.
+ * @param reqExtensions a {@code Map} of request extensions
+ *
+ * @throws IOException if an error happens during encoding
+ * @throws NullPointerException if {@code respStat} is {@code null}
+ * or {@code respStat} is successful, and a {@code null} {@code itemMap}
+ * has been provided.
+ */
+ public LocalOcspResponse(OCSPResponse.ResponseStatus respStat,
+ Map<CertId, CertStatusInfo> itemMap,
+ Map<String, Extension> reqExtensions) throws IOException {
+ responseStatus = Objects.requireNonNull(respStat,
+ "Illegal null response status");
+ if (responseStatus == ResponseStatus.SUCCESSFUL) {
+ respItemMap = Objects.requireNonNull(itemMap,
+ "SUCCESSFUL responses must have a response map");
+ producedAtDate = new Date();
+
+ // Turn the answerd from the response DB query into a list
+ // of single responses.
+ for (CertId id : itemMap.keySet()) {
+ singleResponseList.add(
+ new LocalSingleResponse(id, itemMap.get(id)));
+ }
+
+ responseExtensions = setResponseExtensions(reqExtensions);
+ certificates = new ArrayList<>();
+ if (signerCert != issuerCert) {
+ certificates.add(signerCert);
+ }
+ certificates.add(issuerCert);
+ } else {
+ respItemMap = null;
+ producedAtDate = null;
+ responseExtensions = null;
+ certificates = null;
+ }
+ encodedResponse = this.getBytes();
+ }
+
+ /**
+ * Set the response extensions based on the request extensions
+ * that were received. Right now, this is limited to the
+ * OCSP nonce extension.
+ *
+ * @param reqExts a {@code Map} of zero or more request extensions
+ *
+ * @return a {@code Map} of zero or more response extensions, keyed
+ * by the extension object identifier in {@code String} form.
+ */
+ private Map<String, Extension> setResponseExtensions(
+ Map<String, Extension> reqExts) {
+ Map<String, Extension> respExts = new HashMap<>();
+ String ocspNonceStr = PKIXExtensions.OCSPNonce_Id.toString();
+
+ if (reqExts != null) {
+ for (String id : reqExts.keySet()) {
+ if (id.equals(ocspNonceStr)) {
+ // We found a nonce, add it into the response extensions
+ Extension ext = reqExts.get(id);
+ if (ext != null) {
+ respExts.put(id, ext);
+ log("Added OCSP Nonce to response");
+ } else {
+ log("Error: Found nonce entry, but found null " +
+ "value. Skipping");
+ }
+ }
+ }
+ }
+
+ return respExts;
+ }
+
+ /**
+ * Get the DER-encoded response bytes for this response
+ *
+ * @return a byte array containing the DER-encoded bytes for
+ * the response
+ *
+ * @throws IOException if any encoding errors occur
+ */
+ private byte[] getBytes() throws IOException {
+ DerOutputStream outerSeq = new DerOutputStream();
+ DerOutputStream responseStream = new DerOutputStream();
+ responseStream.putEnumerated(responseStatus.ordinal());
+ if (responseStatus == ResponseStatus.SUCCESSFUL &&
+ respItemMap != null) {
+ encodeResponseBytes(responseStream);
+ }
+
+ // Commit the outermost sequence bytes
+ outerSeq.write(DerValue.tag_Sequence, responseStream);
+ return outerSeq.toByteArray();
+ }
+
+ private void encodeResponseBytes(DerOutputStream responseStream)
+ throws IOException {
+ DerOutputStream explicitZero = new DerOutputStream();
+ DerOutputStream respItemStream = new DerOutputStream();
+
+ respItemStream.putOID(OCSP_BASIC_RESPONSE_OID);
+
+ byte[] basicOcspBytes = encodeBasicOcspResponse();
+ respItemStream.putOctetString(basicOcspBytes);
+ explicitZero.write(DerValue.tag_Sequence, respItemStream);
+ responseStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ true, (byte)0), explicitZero);
+ }
+
+ private byte[] encodeBasicOcspResponse() throws IOException {
+ DerOutputStream outerSeq = new DerOutputStream();
+ DerOutputStream basicORItemStream = new DerOutputStream();
+
+ // Encode the tbsResponse
+ byte[] tbsResponseBytes = encodeTbsResponse();
+ basicORItemStream.write(tbsResponseBytes);
+
+ try {
+ sigAlgId.derEncode(basicORItemStream);
+
+ // Create the signature
+ Signature sig = Signature.getInstance(sigAlgId.getName());
+ sig.initSign(signerKey);
+ sig.update(tbsResponseBytes);
+ signature = sig.sign();
+ basicORItemStream.putBitString(signature);
+ } catch (GeneralSecurityException exc) {
+ err(exc);
+ throw new IOException(exc);
+ }
+
+ // Add certificates
+ try {
+ DerOutputStream certStream = new DerOutputStream();
+ ArrayList<DerValue> certList = new ArrayList<>();
+ if (signerCert != issuerCert) {
+ certList.add(new DerValue(signerCert.getEncoded()));
+ }
+ certList.add(new DerValue(issuerCert.getEncoded()));
+ DerValue[] dvals = new DerValue[certList.size()];
+ certStream.putSequence(certList.toArray(dvals));
+ basicORItemStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ true, (byte)0), certStream);
+ } catch (CertificateEncodingException cex) {
+ err(cex);
+ throw new IOException(cex);
+ }
+
+ // Commit the outermost sequence bytes
+ outerSeq.write(DerValue.tag_Sequence, basicORItemStream);
+ return outerSeq.toByteArray();
+ }
+
+ private byte[] encodeTbsResponse() throws IOException {
+ DerOutputStream outerSeq = new DerOutputStream();
+ DerOutputStream tbsStream = new DerOutputStream();
+
+ // Note: We're not going explicitly assert the version
+ tbsStream.write(respId.getEncoded());
+ tbsStream.putGeneralizedTime(producedAtDate);
+
+ // Sequence of responses
+ encodeSingleResponses(tbsStream);
+
+ // TODO: add response extension support
+ encodeExtensions(tbsStream);
+
+ outerSeq.write(DerValue.tag_Sequence, tbsStream);
+ return outerSeq.toByteArray();
+ }
+
+ private void encodeSingleResponses(DerOutputStream tbsStream)
+ throws IOException {
+ DerValue[] srDerVals = new DerValue[singleResponseList.size()];
+ int srDvCtr = 0;
+
+ for (LocalSingleResponse lsr : singleResponseList) {
+ srDerVals[srDvCtr++] = new DerValue(lsr.getBytes());
+ }
+
+ tbsStream.putSequence(srDerVals);
+ }
+
+ private void encodeExtensions(DerOutputStream tbsStream)
+ throws IOException {
+ DerOutputStream extSequence = new DerOutputStream();
+ DerOutputStream extItems = new DerOutputStream();
+
+ for (Extension ext : responseExtensions.values()) {
+ ext.encode(extItems);
+ }
+ extSequence.write(DerValue.tag_Sequence, extItems);
+ tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,
+ (byte)1), extSequence);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("OCSP Response: ").append(responseStatus).append("\n");
+ if (responseStatus == ResponseStatus.SUCCESSFUL) {
+ sb.append("Response Type: ").
+ append(OCSP_BASIC_RESPONSE_OID.toString()).append("\n");
+ sb.append(String.format("Version: %d (0x%X)", version + 1,
+ version)).append("\n");
+ sb.append("Responder Id: ").append(respId.toString()).
+ append("\n");
+ sb.append("Produced At: ").
+ append(utcDateFmt.format(producedAtDate)).append("\n");
+
+ int srCtr = 0;
+ for (LocalSingleResponse lsr : singleResponseList) {
+ sb.append("SingleResponse [").append(srCtr++).append("]\n");
+ sb.append(lsr);
+ }
+
+ if (!responseExtensions.isEmpty()) {
+ sb.append("Extensions (").append(responseExtensions.size()).
+ append(")\n");
+ for (Extension ext : responseExtensions.values()) {
+ sb.append("\t").append(ext).append("\n");
+ }
+ } else {
+ sb.append("\n");
+ }
+
+ if (signature != null) {
+ sb.append("Signature: ").append(sigAlgId).append("\n");
+ sb.append(dumpHexBytes(signature)).append("\n");
+ int certCtr = 0;
+ for (X509Certificate cert : certificates) {
+ sb.append("Certificate [").append(certCtr++).append("]").
+ append("\n");
+ sb.append("\tSubject: ");
+ sb.append(cert.getSubjectX500Principal()).append("\n");
+ sb.append("\tIssuer: ");
+ sb.append(cert.getIssuerX500Principal()).append("\n");
+ sb.append("\tSerial: ").append(cert.getSerialNumber());
+ sb.append("\n");
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private class LocalSingleResponse {
+ private final CertId certId;
+ private final CertStatusInfo csInfo;
+ private final Date thisUpdate;
+ private final Date lsrNextUpdate;
+ private final Map<String, Extension> singleExtensions;
+
+ public LocalSingleResponse(CertId cid, CertStatusInfo info) {
+ certId = Objects.requireNonNull(cid, "CertId must be non-null");
+ csInfo = Objects.requireNonNull(info,
+ "CertStatusInfo must be non-null");
+
+ // For now, we'll keep things simple and make the thisUpdate
+ // field the same as the producedAt date.
+ thisUpdate = producedAtDate;
+ lsrNextUpdate = getNextUpdate();
+
+ // TODO Add extensions support
+ singleExtensions = Collections.emptyMap();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Certificate Status: ").append(csInfo.getType());
+ sb.append("\n");
+ if (csInfo.getType() == CertStatus.CERT_STATUS_REVOKED) {
+ sb.append("Revocation Time: ");
+ sb.append(utcDateFmt.format(csInfo.getRevocationTime()));
+ sb.append("\n");
+ if (csInfo.getRevocationReason() != null) {
+ sb.append("Revocation Reason: ");
+ sb.append(csInfo.getRevocationReason()).append("\n");
+ }
+ }
+
+ sb.append("CertId, Algorithm = ");
+ sb.append(certId.getHashAlgorithm()).append("\n");
+ sb.append("\tIssuer Name Hash: ");
+ sb.append(dumpHexBytes(certId.getIssuerNameHash(), 256, "", ""));
+ sb.append("\n");
+ sb.append("\tIssuer Key Hash: ");
+ sb.append(dumpHexBytes(certId.getIssuerKeyHash(), 256, "", ""));
+ sb.append("\n");
+ sb.append("\tSerial Number: ").append(certId.getSerialNumber());
+ sb.append("\n");
+ sb.append("This Update: ");
+ sb.append(utcDateFmt.format(thisUpdate)).append("\n");
+ if (lsrNextUpdate != null) {
+ sb.append("Next Update: ");
+ sb.append(utcDateFmt.format(lsrNextUpdate)).append("\n");
+ }
+
+ if (!singleExtensions.isEmpty()) {
+ sb.append("Extensions (").append(singleExtensions.size()).
+ append(")\n");
+ for (Extension ext : singleExtensions.values()) {
+ sb.append("\t").append(ext).append("\n");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public byte[] getBytes() throws IOException {
+ byte[] nullData = { };
+ DerOutputStream responseSeq = new DerOutputStream();
+ DerOutputStream srStream = new DerOutputStream();
+
+ // Encode the CertId
+ certId.encode(srStream);
+
+ // Next, encode the CertStatus field
+ CertStatus csiType = csInfo.getType();
+ switch (csiType) {
+ case CERT_STATUS_GOOD:
+ srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ false, (byte)0), nullData);
+ break;
+ case CERT_STATUS_REVOKED:
+ DerOutputStream revInfo = new DerOutputStream();
+ revInfo.putGeneralizedTime(csInfo.getRevocationTime());
+ CRLReason revReason = csInfo.getRevocationReason();
+ if (revReason != null) {
+ byte[] revDer = new byte[3];
+ revDer[0] = DerValue.tag_Enumerated;
+ revDer[1] = 1;
+ revDer[2] = (byte)revReason.ordinal();
+ revInfo.write(DerValue.createTag(
+ DerValue.TAG_CONTEXT, true, (byte)0),
+ revDer);
+ }
+ srStream.write(DerValue.createTag(
+ DerValue.TAG_CONTEXT, true, (byte)1),
+ revInfo);
+ break;
+ case CERT_STATUS_UNKNOWN:
+ srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ false, (byte)2), nullData);
+ break;
+ default:
+ throw new IOException("Unknown CertStatus: " + csiType);
+ }
+
+ // Add the necessary dates
+ srStream.putGeneralizedTime(thisUpdate);
+ if (lsrNextUpdate != null) {
+ DerOutputStream nuStream = new DerOutputStream();
+ nuStream.putGeneralizedTime(lsrNextUpdate);
+ srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+ true, (byte)0), nuStream);
+ }
+
+ // TODO add singleResponse Extension support
+
+ // Add the single response to the response output stream
+ responseSeq.write(DerValue.tag_Sequence, srStream);
+ return responseSeq.toByteArray();
+ }
+ }
+ }
+}
--- a/jdk/test/javax/net/ssl/DTLS/NoMacInitialClientHello.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/test/javax/net/ssl/DTLS/NoMacInitialClientHello.java Wed Aug 05 12:19:38 2015 -0700
@@ -29,7 +29,8 @@
* @bug 8043758
* @summary Datagram Transport Layer Security (DTLS)
* @compile DTLSOverDatagram.java
- * @run main/othervm NoMacInitialClientHello
+ * @run main/othervm -Djdk.tls.client.enableStatusRequestExtension=false
+ * NoMacInitialClientHello
*/
import java.net.DatagramPacket;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/Stapling/HttpsUrlConnClient.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/Stapling/SSLEngineWithStapling.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/Stapling/SSLSocketWithStapling.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/provider/certpath/Extensions/OCSPNonceExtensionTests.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+ };
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/provider/certpath/ResponderId/ResponderIdTests.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+ };
+
+}
--- a/jdk/test/sun/security/ssl/ExtensionType/OptimalListSize.java Wed Aug 05 11:06:49 2015 -0700
+++ b/jdk/test/sun/security/ssl/ExtensionType/OptimalListSize.java Wed Aug 05 12:19:38 2015 -0700
@@ -36,6 +36,6 @@
public static void main(String[] args) throws Throwable {
OptimalCapacity.ofArrayList(
Class.forName("sun.security.ssl.ExtensionType"),
- "knownExtensions", 13);
+ "knownExtensions", 14);
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/BogusStatusRequest.java Wed Aug 05 12:19:38 2015 -0700
@@ -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";
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/CertStatusReqExtensionTests.java Wed Aug 05 12:19:38 2015 -0700
@@ -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();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/CertStatusReqItemV2Tests.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+ };
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/CertStatusReqListV2ExtensionTests.java Wed Aug 05 12:19:38 2015 -0700
@@ -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();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/OCSPStatusRequestTests.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+ };
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/StatusResponseManagerTests.java Wed Aug 05 12:19:38 2015 -0700
@@ -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);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/TEST.properties Wed Aug 05 12:19:38 2015 -0700
@@ -0,0 +1,1 @@
+bootclasspath.dirs=.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/TestCase.java Wed Aug 05 12:19:38 2015 -0700
@@ -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();
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/ssl/StatusStapling/TestUtils.java Wed Aug 05 12:19:38 2015 -0700
@@ -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;
+ }
+}
+
+