/*
 * Copyright (c) 2003, 2020, 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.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.CRLReason;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
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 java.util.Set;
import javax.security.auth.x500.X500Principal;

import sun.security.util.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
/** * 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: * <pre> * * 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 * * </pre> * * @author Ram Marti */
public final class OCSPResponse { public enum ResponseStatus { SUCCESSFUL, // Response has valid confirmations MALFORMED_REQUEST, // Illegal request INTERNAL_ERROR, // Internal error in responder TRY_LATER, // Try again later UNUSED, // is not used SIG_REQUIRED, // Must sign the request UNAUTHORIZED // Request unauthorized }; 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"); 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 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"; // 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 milliseconds, to be used for the OCSP check.
/** * Integer value indicating the maximum allowable clock skew, * in milliseconds, 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.
/** * 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 final CRLReason[] values = CRLReason.values(); private final ResponseStatus responseStatus; private final Map<CertId, SingleResponse> singleResponseMap; private final AlgorithmId sigAlgId; private final byte[] signature; private final byte[] tbsResponseData; private final byte[] responseNonce; private List<X509CertImpl> certs; private X509CertImpl signerCert = 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 */ public OCSPResponse(byte[] bytes) throws IOException { if (dump) { HexDumpEncoder hexEnc = new HexDumpEncoder(); debug.println("OCSPResponse bytes...\n\n" + 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(); certs = new ArrayList<X509CertImpl>(); sigAlgId = null; signature = null; tbsResponseData = null; responseNonce = null; responseExtensions = Collections.emptyMap(); respId = null; 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(3); if (seqTmp.length < 3) { throw new IOException("Unexpected BasicOCSPResponse value"); } DerValue responseData = seqTmp[0]; // Need the DER encoded ResponseData to verify the signature later tbsResponseData = 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 respId = new ResponderId(seq.toByteArray()); if (debug != null) { debug.println("Responder ID: " + respId); } // producedAt seq = seqDerIn.getDerValue(); producedAtDate = seq.getGeneralizedTime(); if (debug != null) { 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 (DerValue srDer : singleResponseDer) { SingleResponse singleResponse = new SingleResponse(srDer); singleResponseMap.put(singleResponse.getCertId(), singleResponse); } // responseExtensions Map<String, java.security.cert.Extension> tmpExtMap = new HashMap<>(); if (seqDerIn.available() > 0) { seq = seqDerIn.getDerValue(); if (seq.isContextSpecific((byte)1)) { tmpExtMap = parseExtensions(seq); } } 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]); // signature signature = seqTmp[2].getBitString(); // 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[] derCerts = seqCert.getData().getSequence(3); certs = new ArrayList<X509CertImpl>(derCerts.length); try { for (int i = 0; i < derCerts.length; i++) { X509CertImpl cert = new X509CertImpl(derCerts[i].toByteArray()); certs.add(cert); if (debug != null) { debug.println("OCSP response cert #" + (i + 1) + ": " + cert.getSubjectX500Principal()); } } } catch (CertificateException ce) { throw new IOException("Bad encoding in X509 Certificate", ce); } } else { certs = new ArrayList<X509CertImpl>(); } } void verify(List<CertId> certIds, IssuerInfo issuerInfo, X509Certificate responderCert, Date date, byte[] nonce, String variant) throws CertPathValidatorException { switch (responseStatus) { case SUCCESSFUL: break; case TRY_LATER: case INTERNAL_ERROR: throw new CertPathValidatorException( "OCSP response error: " + responseStatus, null, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); case UNAUTHORIZED: default: throw new CertPathValidatorException("OCSP response error: " + responseStatus); } // Check that the response includes a response for all of the // certs that were supplied in the request for (CertId certId : certIds) { SingleResponse sr = getSingleResponse(certId); if (sr == null) { if (debug != null) { debug.println("No response found for CertId: " + certId); } throw new CertPathValidatorException( "OCSP response does not include a response for a " + "certificate supplied in the OCSP request"); } if (debug != null) { debug.println("Status of certificate (with serial number " + certId.getSerialNumber() + ") is: " + sr.getCertStatus()); } } // Locate the signer cert if (signerCert == null) { // Add the Issuing CA cert and/or Trusted Responder cert to the list // of certs from the OCSP response try { if (issuerInfo.getCertificate() != null) { certs.add(X509CertImpl.toImpl(issuerInfo.getCertificate())); } if (responderCert != null) { certs.add(X509CertImpl.toImpl(responderCert)); } } catch (CertificateException ce) { throw new CertPathValidatorException( "Invalid issuer or trusted responder certificate", ce); } if (respId.getType() == ResponderId.Type.BY_NAME) { X500Principal rName = respId.getResponderName(); for (X509CertImpl cert : certs) { if (cert.getSubjectX500Principal().equals(rName)) { signerCert = cert; break; } } } 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 && ridKeyId.equals(certKeyId)) { signerCert = cert; break; } else { // The certificate does not have a SKID or may have // been using a different algorithm (ex: see RFC 7093). // Check if the responder's key identifier matches // against a newly generated key identifier of the // cert's public key using the 160-bit SHA-1 method. try { certKeyId = new KeyIdentifier(cert.getPublicKey()); } catch (IOException e) { // ignore } if (ridKeyId.equals(certKeyId)) { signerCert = cert; break; } } } } } // Check whether the signer cert returned by the responder is trusted if (signerCert != null) { // Check if the response is signed by the issuing CA if (signerCert.getSubjectX500Principal().equals( issuerInfo.getName()) && signerCert.getPublicKey().equals( issuerInfo.getPublicKey())) { if (debug != null) { debug.println("OCSP response is signed by the target's " + "Issuing CA"); } // cert is trusted, now verify the signed response // Check if the response is signed by a trusted responder } else if (signerCert.equals(responderCert)) { if (debug != null) { debug.println("OCSP response is signed by a Trusted " + "Responder"); } // cert is trusted, now verify the signed response // Check if the response is signed by an authorized responder } else if (signerCert.getIssuerX500Principal().equals( issuerInfo.getName())) { // Check for the OCSPSigning key purpose try { List<String> keyPurposes = signerCert.getExtendedKeyUsage(); if (keyPurposes == null || !keyPurposes.contains(KP_OCSP_SIGNING_OID)) { throw new CertPathValidatorException( "Responder's certificate not valid for signing " + "OCSP responses"); } } catch (CertificateParsingException cpe) { // assume cert is not valid for signing throw new CertPathValidatorException( "Responder's certificate not valid for signing " + "OCSP responses", cpe); } // Check algorithm constraints specified in security property // "jdk.certpath.disabledAlgorithms". AlgorithmChecker algChecker = new AlgorithmChecker(issuerInfo.getAnchor(), date, variant); algChecker.init(false); algChecker.check(signerCert, Collections.<String>emptySet()); // check the validity try { if (date == null) { signerCert.checkValidity(); } else { signerCert.checkValidity(date); } } catch (CertificateException e) { throw new CertPathValidatorException( "Responder's certificate not within the " + "validity period", e); } // 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(issuerInfo.getPublicKey()); if (debug != null) { debug.println("OCSP response is signed by an " + "Authorized Responder"); } // cert is trusted, now verify the signed response } catch (GeneralSecurityException e) { signerCert = null; } } else { throw new CertPathValidatorException( "Responder's certificate is not authorized to sign " + "OCSP responses"); } } // Confirm that the signed response was generated using the public // key from the trusted responder cert if (signerCert != null) { // Check algorithm constraints specified in security property // "jdk.certpath.disabledAlgorithms". AlgorithmChecker.check(signerCert.getPublicKey(), sigAlgId, variant); if (!verifySignature(signerCert)) { throw new CertPathValidatorException( "Error verifying OCSP Response's signature"); } } else { // Need responder's cert in order to verify the signature throw new CertPathValidatorException( "Unable to verify OCSP Response's signature"); } if (nonce != null) { if (responseNonce != null && !Arrays.equals(nonce, responseNonce)) { throw new CertPathValidatorException("Nonces don't match"); } } // 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); for (SingleResponse sr : singleResponseMap.values()) { if (debug != null) { String until = ""; if (sr.nextUpdate != null) { until = " until " + sr.nextUpdate; } debug.println("OCSP response validity interval is from " + sr.thisUpdate + until); debug.println("Checking validity of OCSP response on " + new Date(now) + " with allowed interval between " + nowMinusSkew + " and " + nowPlusSkew); } // 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( sr.nextUpdate != null ? sr.nextUpdate : sr.thisUpdate)) { throw new CertPathValidatorException( "Response is unreliable: its validity " + "interval is out-of-date"); } } }
Returns the OCSP ResponseStatus.
Returns:the ResponseStatus for this OCSP response
/** * Returns the OCSP ResponseStatus. * * @return the {@code ResponseStatus} for this OCSP response */
public ResponseStatus getResponseStatus() { return responseStatus; } /* * Verify the signature of the OCSP response. */ private boolean verifySignature(X509Certificate cert) throws CertPathValidatorException { try { Signature respSignature = Signature.getInstance(sigAlgId.getName()); respSignature.initVerify(cert.getPublicKey()); respSignature.update(tbsResponseData); if (respSignature.verify(signature)) { if (debug != null) { debug.println("Verified signature of OCSP Response"); } return true; } else { if (debug != null) { debug.println( "Error verifying signature of OCSP Response"); } return false; } } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) { throw new CertPathValidatorException(e); } }
Returns the SingleResponse of the specified CertId, or null if there is no response for that CertId.
Params:
  • certId – the CertId for a SingleResponse to be searched for in the OCSP response.
Returns:the SingleResponse for the provided CertId, or null if it is not found.
/** * 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. */
public SingleResponse getSingleResponse(CertId certId) { return singleResponseMap.get(certId); }
Return a set of all CertIds in this OCSPResponse
Returns:an unmodifiable set containing every CertId in this response.
/** * 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. */ X509Certificate getSignerCertificate() { return signerCert; // set in verify() }
Get the ResponderId from this OCSPResponse
Returns:the ResponderId from this response or null if no responder ID is in the body of the response (e.g. a response with a status other than SUCCESS.
/** * 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
Returns:a human-readable representation of the OCSPResponse
/** * 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.
Params:
  • derVal – A DerValue object built from a SEQUENCE of extensions
Throws:
Returns:a 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 Map will be returned.
/** * 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 * extensions are found or an empty SEQUENCE is passed in, then * an empty {@code Map} will be returned. * * @throws IOException if any decoding errors occur. */
private static Map<String, java.security.cert.Extension> parseExtensions(DerValue derVal) throws IOException { DerValue[] extDer = derVal.data.getSequence(3); Map<String, java.security.cert.Extension> extMap = new HashMap<>(extDer.length); 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. if (ext.isCritical()) { throw new IOException("Unsupported OCSP critical extension: " + ext.getExtensionId()); } extMap.put(ext.getId(), ext); } return extMap; } /* * A class representing a single OCSP response. */ public static final 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<String, java.security.cert.Extension> singleExtensions; private SingleResponse(DerValue der) 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 = null; 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 (debug != null) { debug.println("thisUpdate: " + thisUpdate); } // Parse optional fields like nextUpdate and singleExtensions Date tmpNextUpdate = null; Map<String, java.security.cert.Extension> tmpMap = null; // Check for the first optional item, it could be nextUpdate // [CONTEXT 0] or singleExtensions [CONTEXT 1] if (tmp.available() > 0) { derVal = tmp.getDerValue(); // nextUpdate processing if (derVal.isContextSpecific((byte)0)) { tmpNextUpdate = derVal.data.getGeneralizedTime(); if (debug != null) { debug.println("nextUpdate: " + tmpNextUpdate); } // If more data exists in the singleResponse, it // can only be singleExtensions. Get this DER value // for processing in the next block derVal = tmp.available() > 0 ? tmp.getDerValue() : null; } // singleExtensions processing if (derVal != null) { if (derVal.isContextSpecific((byte)1)) { tmpMap = parseExtensions(derVal); // There should not be any other items in the // singleResponse at this point. if (tmp.available() > 0) { throw new IOException(tmp.available() + " bytes of additional data in singleResponse"); } } else { // Unknown item in the singleResponse throw new IOException("Unsupported singleResponse " + "item, tag = " + String.format("%02X", derVal.tag)); } } } nextUpdate = tmpNextUpdate; singleExtensions = (tmpMap != null) ? tmpMap : Collections.emptyMap(); if (debug != null) { for (java.security.cert.Extension ext : singleExtensions.values()) { debug.println("singleExtension: " + ext); } } } /* * Return the certificate's revocation status code */ @Override public CertStatus getCertStatus() { return certStatus; }
Get the Cert ID that this SingleResponse is for.
Returns:the CertId for this SingleResponse
/** * Get the Cert ID that this SingleResponse is for. * * @return the {@code CertId} for this {@code SingleResponse} */
public CertId getCertId() { return certId; }
Get the thisUpdate field from this SingleResponse.
Returns:a Date object containing the thisUpdate date
/** * 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 nextUpdate field from this SingleResponse.
Returns:a Date object containing the nexUpdate date or null if a nextUpdate field is not present in the response.
/** * 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 revocationTime field from this SingleResponse.
Returns:a Date object containing the revocationTime date or null if the SingleResponse does not have a status of REVOKED.
/** * 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); }
Get the revocationReason field for the SingleResponse.
Returns:a CRLReason containing the revocation reason, or null if a revocation reason was not provided or the response status is not REVOKED.
/** * 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 singleExtensions for this SingleResponse.
Returns:a Map of Extension objects, keyed by their OID value in string form.
/** * 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); }
Construct a string representation of a single OCSP response.
/** * 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: ").append(certStatus).append("\n"); if (certStatus == CertStatus.REVOKED) { sb.append("revocationTime is "); sb.append(revocationTime).append("\n"); sb.append("revocationReason is "); sb.append(revocationReason).append("\n"); } sb.append("thisUpdate is ").append(thisUpdate).append("\n"); if (nextUpdate != null) { sb.append("nextUpdate is ").append(nextUpdate).append("\n"); } for (java.security.cert.Extension ext : singleExtensions.values()) { sb.append("singleExtension: "); sb.append(ext.toString()).append("\n"); } return sb.toString(); } }
Helper class that allows consumers to pass in issuer information. This will always consist of the issuer's name and public key, but may also contain a certificate if the originating data is in that form. The trust anchor for the certificate chain will be included for certpath disabled algorithm checking.
/** * Helper class that allows consumers to pass in issuer information. This * will always consist of the issuer's name and public key, but may also * contain a certificate if the originating data is in that form. The * trust anchor for the certificate chain will be included for certpath * disabled algorithm checking. */
static final class IssuerInfo { private final TrustAnchor anchor; private final X509Certificate certificate; private final X500Principal name; private final PublicKey pubKey; IssuerInfo(TrustAnchor anchor) { this(anchor, (anchor != null) ? anchor.getTrustedCert() : null); } IssuerInfo(X509Certificate issuerCert) { this(null, issuerCert); } IssuerInfo(TrustAnchor anchor, X509Certificate issuerCert) { if (anchor == null && issuerCert == null) { throw new NullPointerException("TrustAnchor and issuerCert " + "cannot be null"); } this.anchor = anchor; if (issuerCert != null) { name = issuerCert.getSubjectX500Principal(); pubKey = issuerCert.getPublicKey(); certificate = issuerCert; } else { name = anchor.getCA(); pubKey = anchor.getCAPublicKey(); certificate = anchor.getTrustedCert(); } }
Get the certificate in this IssuerInfo if present.
Returns:the X509Certificate used to create this IssuerInfo object, or null if a certificate was not used in its creation.
/** * Get the certificate in this IssuerInfo if present. * * @return the {@code X509Certificate} used to create this IssuerInfo * object, or {@code null} if a certificate was not used in its * creation. */
X509Certificate getCertificate() { return certificate; }
Get the name of this issuer.
Returns:an X500Principal corresponding to this issuer's name. If derived from an issuer's X509Certificate this would be equivalent to the certificate subject name.
/** * Get the name of this issuer. * * @return an {@code X500Principal} corresponding to this issuer's * name. If derived from an issuer's {@code X509Certificate} this * would be equivalent to the certificate subject name. */
X500Principal getName() { return name; }
Get the public key for this issuer.
Returns:a PublicKey for this issuer.
/** * Get the public key for this issuer. * * @return a {@code PublicKey} for this issuer. */
PublicKey getPublicKey() { return pubKey; }
Get the TrustAnchor for the certificate chain.
Returns:a TrustAnchor.
/** * Get the TrustAnchor for the certificate chain. * * @return a {@code TrustAnchor}. */
TrustAnchor getAnchor() { return anchor; }
Create a string representation of this IssuerInfo.
Returns:a String form of this IssuerInfo object.
/** * Create a string representation of this IssuerInfo. * * @return a {@code String} form of this IssuerInfo object. */
@Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Issuer Info:\n"); sb.append("Name: ").append(name.toString()).append("\n"); sb.append("Public Key:\n").append(pubKey.toString()).append("\n"); return sb.toString(); } } }