/* * 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. 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.*; import java.math.BigInteger; import java.security.*; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.security.cert.CertPathValidatorException; import java.security.cert.CRLReason; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import sun.misc.HexDumpEncoder; import sun.security.action.GetIntegerAction; import sun.security.x509.*; import sun.security.util.*; /** * This class is used to process an OCSP response. * The OCSP Response is defined * in RFC 2560 and the ASN.1 encoding is as follows: *
 *
 *  OCSPResponse ::= SEQUENCE {
 *      responseStatus         OCSPResponseStatus,
 *      responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }
 *
 *   OCSPResponseStatus ::= ENUMERATED {
 *       successful            (0),  --Response has valid confirmations
 *       malformedRequest      (1),  --Illegal confirmation request
 *       internalError         (2),  --Internal error in issuer
 *       tryLater              (3),  --Try again later
 *                                   --(4) is not used
 *       sigRequired           (5),  --Must sign the request
 *       unauthorized          (6)   --Request unauthorized
 *   }
 *
 *   ResponseBytes ::=       SEQUENCE {
 *       responseType   OBJECT IDENTIFIER,
 *       response       OCTET STRING }
 *
 *   BasicOCSPResponse       ::= SEQUENCE {
 *      tbsResponseData      ResponseData,
 *      signatureAlgorithm   AlgorithmIdentifier,
 *      signature            BIT STRING,
 *      certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
 *
 *   The value for signature SHALL be computed on the hash of the DER
 *   encoding ResponseData.
 *
 *   ResponseData ::= SEQUENCE {
 *      version              [0] EXPLICIT Version DEFAULT v1,
 *      responderID              ResponderID,
 *      producedAt               GeneralizedTime,
 *      responses                SEQUENCE OF SingleResponse,
 *      responseExtensions   [1] EXPLICIT Extensions OPTIONAL }
 *
 *   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)
 *
 *   SingleResponse ::= SEQUENCE {
 *      certID                       CertID,
 *      certStatus                   CertStatus,
 *      thisUpdate                   GeneralizedTime,
 *      nextUpdate         [0]       EXPLICIT GeneralizedTime OPTIONAL,
 *      singleExtensions   [1]       EXPLICIT Extensions OPTIONAL }
 *
 *   CertStatus ::= CHOICE {
 *       good        [0]     IMPLICIT NULL,
 *       revoked     [1]     IMPLICIT RevokedInfo,
 *       unknown     [2]     IMPLICIT UnknownInfo }
 *
 *   RevokedInfo ::= SEQUENCE {
 *       revocationTime              GeneralizedTime,
 *       revocationReason    [0]     EXPLICIT CRLReason OPTIONAL }
 *
 *   UnknownInfo ::= NULL -- this can be replaced with an enumeration
 *
 * 
* * @author Ram Marti */ public final class OCSPResponse { public enum ResponseStatus { SUCCESSFUL, // Response has valid confirmations MALFORMED_REQUEST, // Illegal confirmation request INTERNAL_ERROR, // Internal error in issuer TRY_LATER, // Try again later UNUSED, // is not used SIG_REQUIRED, // Must sign the request UNAUTHORIZED // Request unauthorized }; private static ResponseStatus[] rsvalues = ResponseStatus.values(); private static final Debug DEBUG = Debug.getInstance("certpath"); private static final boolean dump = DEBUG.isOn("ocsp"); 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 ObjectIdentifier OCSP_NONCE_EXTENSION_OID = ObjectIdentifier.newInternal(new int[] { 1, 3, 6, 1, 5, 5, 7, 48, 1, 2}); private static final int CERT_STATUS_GOOD = 0; private static final int CERT_STATUS_REVOKED = 1; private static final int CERT_STATUS_UNKNOWN = 2; // ResponderID CHOICE tags private static final int NAME_TAG = 1; private static final int KEY_TAG = 2; // Object identifier for the OCSPSigning key purpose private static final String KP_OCSP_SIGNING_OID = "1.3.6.1.5.5.7.3.9"; private final ResponseStatus responseStatus; private final Map singleResponseMap; // Default maximum clock skew in milliseconds (15 minutes) // allowed when checking validity of OCSP responses private static final int DEFAULT_MAX_CLOCK_SKEW = 900000; /** * Integer value indicating the maximum allowable clock skew, in seconds, * to be used for the OCSP check. */ private static final int MAX_CLOCK_SKEW = initializeClockSkew(); /** * Initialize the maximum allowable clock skew by getting the OCSP * clock skew system property. If the property has not been set, or if its * value is negative, set the skew to the default. */ private static int initializeClockSkew() { Integer tmp = java.security.AccessController.doPrivileged( new GetIntegerAction("com.sun.security.ocsp.clockSkew")); if (tmp == null || tmp < 0) { return DEFAULT_MAX_CLOCK_SKEW; } // Convert to milliseconds, as the system property will be // specified in seconds return tmp * 1000; } // an array of all of the CRLReasons (used in SingleResponse) private static CRLReason[] values = CRLReason.values(); /* * Create an OCSP response from its ASN.1 DER encoding. */ OCSPResponse(byte[] bytes, Date dateCheckedAgainst, List responderCerts) throws IOException, CertPathValidatorException { // OCSPResponse if (dump) { HexDumpEncoder hexEnc = new HexDumpEncoder(); DEBUG.println("\nOCSPResponse bytes..."); DEBUG.println(hexEnc.encode(bytes) + "\n"); } DerValue der = new DerValue(bytes); if (der.tag != DerValue.tag_Sequence) { throw new IOException("Bad encoding in OCSP response: " + "expected ASN.1 SEQUENCE tag."); } DerInputStream derIn = der.getData(); // responseStatus int status = derIn.getEnumerated(); if (status >= 0 && status < rsvalues.length) { responseStatus = rsvalues[status]; } else { // unspecified responseStatus throw new IOException("Unknown OCSPResponse status: " + status); } if (DEBUG != null) { DEBUG.println("OCSP response status: " + responseStatus); } if (responseStatus != ResponseStatus.SUCCESSFUL) { // no need to continue, responseBytes are not set. singleResponseMap = Collections.emptyMap(); return; } // responseBytes der = derIn.getDerValue(); if (!der.isContextSpecific((byte)0)) { throw new IOException("Bad encoding in responseBytes element " + "of OCSP response: expected ASN.1 context specific tag 0."); } DerValue tmp = der.data.getDerValue(); if (tmp.tag != DerValue.tag_Sequence) { throw new IOException("Bad encoding in responseBytes element " + "of OCSP response: expected ASN.1 SEQUENCE tag."); } // responseType derIn = tmp.data; ObjectIdentifier responseType = derIn.getOID(); if (responseType.equals((Object)OCSP_BASIC_RESPONSE_OID)) { if (DEBUG != null) { DEBUG.println("OCSP response type: basic"); } } else { if (DEBUG != null) { DEBUG.println("OCSP response type: " + responseType); } throw new IOException("Unsupported OCSP response type: " + responseType); } // BasicOCSPResponse DerInputStream basicOCSPResponse = new DerInputStream(derIn.getOctetString()); DerValue[] seqTmp = basicOCSPResponse.getSequence(2); if (seqTmp.length < 3) { throw new IOException("Unexpected BasicOCSPResponse value"); } DerValue responseData = seqTmp[0]; // Need the DER encoded ResponseData to verify the signature later byte[] responseDataDer = seqTmp[0].toByteArray(); // tbsResponseData if (responseData.tag != DerValue.tag_Sequence) { throw new IOException("Bad encoding in tbsResponseData " + "element of OCSP response: expected ASN.1 SEQUENCE tag."); } DerInputStream seqDerIn = responseData.data; DerValue seq = seqDerIn.getDerValue(); // version if (seq.isContextSpecific((byte)0)) { // seq[0] is version if (seq.isConstructed() && seq.isContextSpecific()) { //System.out.println ("version is available"); seq = seq.data.getDerValue(); int version = seq.getInteger(); if (seq.data.available() != 0) { throw new IOException("Bad encoding in version " + " element of OCSP response: bad format"); } seq = seqDerIn.getDerValue(); } } // responderID short tag = (byte)(seq.tag & 0x1f); if (tag == NAME_TAG) { if (DEBUG != null) { X500Name responderName = new X500Name(seq.getData()); DEBUG.println("OCSP Responder name: " + responderName); } } else if (tag == KEY_TAG) { seq = seq.data.getDerValue(); // consume tag and length if (DEBUG != null) { byte[] responderKeyId = seq.getOctetString(); DEBUG.println("OCSP Responder key ID: " + String.format("0x%0" + (responderKeyId.length * 2) + "x", new BigInteger(1, responderKeyId))); } } else { throw new IOException("Bad encoding in responderID element of " + "OCSP response: expected ASN.1 context specific tag 1 or 2"); } // producedAt seq = seqDerIn.getDerValue(); if (DEBUG != null) { Date producedAtDate = seq.getGeneralizedTime(); DEBUG.println("OCSP response produced at: " + producedAtDate); } // responses DerValue[] singleResponseDer = seqDerIn.getSequence(1); singleResponseMap = new HashMap(singleResponseDer.length); if (DEBUG != null) { DEBUG.println("OCSP number of SingleResponses: " + singleResponseDer.length); } for (int i = 0; i < singleResponseDer.length; i++) { SingleResponse singleResponse = new SingleResponse(singleResponseDer[i], dateCheckedAgainst); singleResponseMap.put(singleResponse.getCertId(), singleResponse); } // responseExtensions 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 responseExtension = new Extension(responseExtDer[i]); if (DEBUG != null) { DEBUG.println("OCSP extension: " + responseExtension); } if (responseExtension.getExtensionId().equals((Object) OCSP_NONCE_EXTENSION_OID)) { /* ocspNonce = responseExtension[i].getExtensionValue(); */ } else if (responseExtension.isCritical()) { throw new IOException( "Unsupported OCSP critical extension: " + responseExtension.getExtensionId()); } } } } // signatureAlgorithmId AlgorithmId sigAlgId = AlgorithmId.parse(seqTmp[1]); // signature byte[] signature = seqTmp[2].getBitString(); X509CertImpl[] x509Certs = null; // if seq[3] is available , then it is a sequence of certificates if (seqTmp.length > 3) { // certs are available DerValue seqCert = seqTmp[3]; if (!seqCert.isContextSpecific((byte)0)) { throw new IOException("Bad encoding in certs element of " + "OCSP response: expected ASN.1 context specific tag 0."); } DerValue[] certs = seqCert.getData().getSequence(3); x509Certs = new X509CertImpl[certs.length]; try { for (int i = 0; i < certs.length; i++) { x509Certs[i] = new X509CertImpl(certs[i].toByteArray()); } } catch (CertificateException ce) { throw new IOException("Bad encoding in X509 Certificate", ce); } } // By default, the OCSP responder's cert is the same as the issuer of // the cert being validated. The issuer cert is the first in the list. X509Certificate trustedResponderCert = responderCerts.get(0); // Check whether the signer cert returned by the responder is trusted if (x509Certs != null && x509Certs[0] != null) { X509CertImpl signerCert = x509Certs[0]; if (DEBUG != null) { DEBUG.println("Signer certificate name: " + signerCert.getSubjectX500Principal()); byte[] signerKeyId = signerCert.getSubjectKeyIdentifier(); if (signerKeyId != null) { DEBUG.println("Signer certificate key ID: " + String.format("0x%0" + (signerKeyId.length * 2) + "x", new BigInteger(1, signerKeyId))); } } byte[] certIssuerKeyId = null; for (X509Certificate responderCert : responderCerts) { // First check if signer cert matches a trusted responder cert if (signerCert.equals(responderCert)) { // signer cert is trusted, now verify the signed response trustedResponderCert = responderCert; if (DEBUG != null) { DEBUG.println("Signer certificate is a trusted " + "responder"); } break; // Next check if signer cert was issued by a trusted responder // cert } else if (signerCert.getIssuerX500Principal().equals( responderCert.getSubjectX500Principal())) { // Retrieve the issuer's key identifier if (certIssuerKeyId == null) { certIssuerKeyId = signerCert.getIssuerKeyIdentifier(); if (certIssuerKeyId == null) { if (DEBUG != null) { DEBUG.println("No issuer key identifier (AKID) " + "in the signer certificate"); } } } // Check that the key identifiers match, if both are present byte[] responderKeyId = null; if (certIssuerKeyId != null && (responderKeyId = OCSPChecker.getKeyId(responderCert)) != null) { if (!Arrays.equals(certIssuerKeyId, responderKeyId)) { continue; // try next cert } if (DEBUG != null) { DEBUG.println("Issuer certificate key ID: " + String.format("0x%0" + (certIssuerKeyId.length * 2) + "x", new BigInteger(1, certIssuerKeyId))); } } // Check for the OCSPSigning key purpose try { List keyPurposes = signerCert.getExtendedKeyUsage(); if (keyPurposes == null || !keyPurposes.contains(KP_OCSP_SIGNING_OID)) { continue; // try next cert } } catch (CertificateParsingException cpe) { continue; // try next cert } // Check algorithm constraints specified in security // property "jdk.certpath.disabledAlgorithms". AlgorithmChecker algChecker = new AlgorithmChecker( new TrustAnchor(responderCert, null)); algChecker.init(false); algChecker.check(signerCert, Collections.emptySet()); // Check the date validity try { if (dateCheckedAgainst == null) { signerCert.checkValidity(); } else { signerCert.checkValidity(dateCheckedAgainst); } } catch (GeneralSecurityException e) { if (DEBUG != null) { DEBUG.println("Responder's certificate not within" + " the validity period " + e); } continue; // try next cert } // Check for revocation // // A CA may specify that an OCSP client can trust a // responder for the lifetime of the responder's // certificate. The CA does so by including the // extension id-pkix-ocsp-nocheck. // Extension noCheck = signerCert.getExtension(PKIXExtensions.OCSPNoCheck_Id); if (noCheck != null) { if (DEBUG != null) { DEBUG.println("Responder's certificate includes " + "the extension id-pkix-ocsp-nocheck."); } } else { // we should do the revocation checking of the // authorized responder in a future update. } // Verify the signature try { signerCert.verify(responderCert.getPublicKey()); trustedResponderCert = signerCert; // cert is trusted, now verify the signed response if (DEBUG != null) { DEBUG.println("Signer certificate was issued by " + "a trusted responder"); } break; } catch (GeneralSecurityException e) { trustedResponderCert = null; } } } } // Confirm that the signed response was generated using the public // key from the trusted responder cert if (trustedResponderCert != null) { // Check algorithm constraints specified in security property // "jdk.certpath.disabledAlgorithms". AlgorithmChecker.check(trustedResponderCert.getPublicKey(), sigAlgId); if (!verifyResponse(responseDataDer, trustedResponderCert, sigAlgId, signature)) { throw new CertPathValidatorException( "Error verifying OCSP Responder's signature"); } } else { // Need responder's cert in order to verify the signature throw new CertPathValidatorException( "Responder's certificate is not trusted for signing " + "OCSP responses"); } } /** * Returns the OCSP ResponseStatus. */ ResponseStatus getResponseStatus() { return responseStatus; } /* * Verify the signature of the OCSP response. * The responder's cert is implicitly trusted. */ private boolean verifyResponse(byte[] responseData, X509Certificate cert, AlgorithmId sigAlgId, byte[] signBytes) throws CertPathValidatorException { try { Signature respSignature = Signature.getInstance(sigAlgId.getName()); respSignature.initVerify(cert.getPublicKey()); respSignature.update(responseData); if (respSignature.verify(signBytes)) { if (DEBUG != null) { DEBUG.println("Verified signature of OCSP Responder"); } return true; } else { if (DEBUG != null) { DEBUG.println( "Error verifying signature of OCSP Responder"); } return false; } } catch (InvalidKeyException ike) { throw new CertPathValidatorException(ike); } catch (NoSuchAlgorithmException nsae) { throw new CertPathValidatorException(nsae); } catch (SignatureException se) { throw new CertPathValidatorException(se); } } /** * Returns the SingleResponse of the specified CertId, or null if * there is no response for that CertId. */ SingleResponse getSingleResponse(CertId certId) { return singleResponseMap.get(certId); } /* * A class representing a single OCSP response. */ final static class SingleResponse implements OCSP.RevocationStatus { private final CertId certId; private final CertStatus certStatus; private final Date thisUpdate; private final Date nextUpdate; private final Date revocationTime; private final CRLReason revocationReason; private final Map singleExtensions; private SingleResponse(DerValue der) throws IOException { this(der, null); } private SingleResponse(DerValue der, Date dateCheckedAgainst) throws IOException { if (der.tag != DerValue.tag_Sequence) { throw new IOException("Bad ASN.1 encoding in SingleResponse"); } DerInputStream tmp = der.data; certId = new CertId(tmp.getDerValue().data); DerValue derVal = tmp.getDerValue(); short tag = (byte)(derVal.tag & 0x1f); if (tag == CERT_STATUS_REVOKED) { certStatus = CertStatus.REVOKED; revocationTime = derVal.data.getGeneralizedTime(); if (derVal.data.available() != 0) { DerValue dv = derVal.data.getDerValue(); tag = (byte)(dv.tag & 0x1f); if (tag == 0) { int reason = dv.data.getEnumerated(); // if reason out-of-range just leave as UNSPECIFIED if (reason >= 0 && reason < values.length) { revocationReason = values[reason]; } else { revocationReason = CRLReason.UNSPECIFIED; } } else { revocationReason = CRLReason.UNSPECIFIED; } } else { revocationReason = CRLReason.UNSPECIFIED; } // RevokedInfo if (DEBUG != null) { DEBUG.println("Revocation time: " + revocationTime); DEBUG.println("Revocation reason: " + revocationReason); } } else { revocationTime = null; revocationReason = CRLReason.UNSPECIFIED; if (tag == CERT_STATUS_GOOD) { certStatus = CertStatus.GOOD; } else if (tag == CERT_STATUS_UNKNOWN) { certStatus = CertStatus.UNKNOWN; } else { throw new IOException("Invalid certificate status"); } } thisUpdate = tmp.getGeneralizedTime(); if (tmp.available() == 0) { // we are done nextUpdate = null; } else { derVal = tmp.getDerValue(); tag = (byte)(derVal.tag & 0x1f); if (tag == 0) { // next update nextUpdate = derVal.data.getGeneralizedTime(); if (tmp.available() == 0) { // we are done } else { derVal = tmp.getDerValue(); tag = (byte)(derVal.tag & 0x1f); } } else { nextUpdate = null; } } // singleExtensions if (tmp.available() > 0) { derVal = tmp.getDerValue(); if (derVal.isContextSpecific((byte)1)) { DerValue[] singleExtDer = derVal.data.getSequence(3); singleExtensions = new HashMap (singleExtDer.length); for (int i = 0; i < singleExtDer.length; i++) { Extension ext = new Extension(singleExtDer[i]); if (DEBUG != null) { DEBUG.println("OCSP single 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. if (ext.isCritical()) { throw new IOException( "Unsupported OCSP critical extension: " + ext.getExtensionId()); } singleExtensions.put(ext.getId(), ext); } } else { singleExtensions = Collections.emptyMap(); } } else { singleExtensions = Collections.emptyMap(); } long now = (dateCheckedAgainst == null) ? System.currentTimeMillis() : dateCheckedAgainst.getTime(); Date nowPlusSkew = new Date(now + MAX_CLOCK_SKEW); Date nowMinusSkew = new Date(now - MAX_CLOCK_SKEW); if (DEBUG != null) { String until = ""; if (nextUpdate != null) { until = " until " + nextUpdate; } DEBUG.println("Response's validity interval is from " + thisUpdate + until); } // Check that the test date is within the validity interval if ((thisUpdate != null && nowPlusSkew.before(thisUpdate)) || (nextUpdate != null && nowMinusSkew.after(nextUpdate))) { if (DEBUG != null) { DEBUG.println("Response is unreliable: its validity " + "interval is out-of-date"); } throw new IOException("Response is unreliable: its validity " + "interval is out-of-date"); } } /* * Return the certificate's revocation status code */ @Override public CertStatus getCertStatus() { return certStatus; } private CertId getCertId() { return certId; } @Override public Date getRevocationTime() { return (Date) revocationTime.clone(); } @Override public CRLReason getRevocationReason() { return revocationReason; } @Override public Map getSingleExtensions() { return Collections.unmodifiableMap(singleExtensions); } /** * Construct a string representation of a single OCSP response. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("SingleResponse: \n"); sb.append(certId); sb.append("\nCertStatus: "+ certStatus + "\n"); if (certStatus == CertStatus.REVOKED) { sb.append("revocationTime is " + revocationTime + "\n"); sb.append("revocationReason is " + revocationReason + "\n"); } sb.append("thisUpdate is " + thisUpdate + "\n"); if (nextUpdate != null) { sb.append("nextUpdate is " + nextUpdate + "\n"); } return sb.toString(); } } }