@SuppressWarnings("deprecation") private byte[] extractKeyData(DerInputStream stream) throws IOException, NoSuchAlgorithmException, CertificateException { byte[] returnValue = null; DerValue[] safeBags = stream.getSequence(2); int count = safeBags.length; /* * Spin over the SafeBags. */ for (int i = 0; i < count; i++) { ObjectIdentifier bagId; DerInputStream sbi; DerValue bagValue; Object bagItem = null; sbi = safeBags[i].toDerInputStream(); bagId = sbi.getOID(); bagValue = sbi.getDerValue(); if (!bagValue.isContextSpecific((byte) 0)) { throw new IOException("unsupported PKCS12 bag value type " + bagValue.tag); } bagValue = bagValue.data.getDerValue(); if (bagId.equals(PKCS8ShroudedKeyBag_OID)) { // got what we were looking for. Return it. returnValue = bagValue.toByteArray(); } else { // log error message for "unsupported PKCS12 bag type" System.out.println("Unsupported bag type '" + bagId + "'"); } } return returnValue; }
// Makes sure serialized form can be deserialized private static void check(byte[] in, String oid) throws Exception { ObjectIdentifier o = (ObjectIdentifier) (new ObjectInputStream(new ByteArrayInputStream(in)).readObject()); if (!o.toString().equals(oid)) { throw new Exception("Read Fail " + o + ", not " + oid); } }
@SuppressWarnings("deprecation") private byte[] fetchPrivateKeyFromBag(byte[] privateKeyInfo) throws IOException, NoSuchAlgorithmException, CertificateException { byte[] returnValue = null; DerValue val = new DerValue(new ByteArrayInputStream(privateKeyInfo)); DerInputStream s = val.toDerInputStream(); int version = s.getInteger(); if (version != 3) { throw new IOException("PKCS12 keystore not in version 3 format"); } /* * Read the authSafe. */ byte[] authSafeData; ContentInfo authSafe = new ContentInfo(s); ObjectIdentifier contentType = authSafe.getContentType(); if (contentType.equals(ContentInfo.DATA_OID)) { authSafeData = authSafe.getData(); } else /* signed data */ { throw new IOException("public key protected PKCS12 not supported"); } DerInputStream as = new DerInputStream(authSafeData); DerValue[] safeContentsArray = as.getSequence(2); int count = safeContentsArray.length; /* * Spin over the ContentInfos. */ for (int i = 0; i < count; i++) { byte[] safeContentsData; ContentInfo safeContents; DerInputStream sci; byte[] eAlgId = null; sci = new DerInputStream(safeContentsArray[i].toByteArray()); safeContents = new ContentInfo(sci); contentType = safeContents.getContentType(); safeContentsData = null; if (contentType.equals(ContentInfo.DATA_OID)) { safeContentsData = safeContents.getData(); } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) { // The password was used to export the private key from the keychain. // The Keychain won't export the key with encrypted data, so we don't need // to worry about it. continue; } else { throw new IOException("public key protected PKCS12" + " not supported"); } DerInputStream sc = new DerInputStream(safeContentsData); returnValue = extractKeyData(sc); } return returnValue; }
/** * Returns the key associated with the given alias, using the given password to recover it. * * @param alias the alias name * @param password the password for recovering the key. This password is used internally as the * key is exported in a PKCS12 format. * @return the requested key, or null if the given alias does not exist or does not identify a * <i>key entry</i>. * @exception NoSuchAlgorithmException if the algorithm for recovering the key cannot be found * @exception UnrecoverableKeyException if the key cannot be recovered (e.g., the given password * is wrong). */ public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { permissionCheck(); // An empty password is rejected by MacOS API, no private key data // is exported. If no password is passed (as is the case when // this implementation is used as browser keystore in various // deployment scenarios like Webstart, JFX and applets), create // a dummy password so MacOS API is happy. if (password == null || password.length == 0) { // Must not be a char array with only a 0, as this is an empty // string. if (random == null) { random = new SecureRandom(); } password = Long.toString(random.nextLong()).toCharArray(); } Object entry = entries.get(alias.toLowerCase()); if (entry == null || !(entry instanceof KeyEntry)) { return null; } // This call gives us a PKCS12 bag, with the key inside it. byte[] exportedKeyInfo = _getEncodedKeyData(((KeyEntry) entry).keyRef, password); if (exportedKeyInfo == null) { return null; } PrivateKey returnValue = null; try { byte[] pkcs8KeyData = fetchPrivateKeyFromBag(exportedKeyInfo); byte[] encryptedKey; AlgorithmParameters algParams; ObjectIdentifier algOid; try { // get the encrypted private key EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(pkcs8KeyData); encryptedKey = encrInfo.getEncryptedData(); // parse Algorithm parameters DerValue val = new DerValue(encrInfo.getAlgorithm().encode()); DerInputStream in = val.toDerInputStream(); algOid = in.getOID(); algParams = parseAlgParameters(in); } catch (IOException ioe) { UnrecoverableKeyException uke = new UnrecoverableKeyException( "Private key not stored as " + "PKCS#8 EncryptedPrivateKeyInfo: " + ioe); uke.initCause(ioe); throw uke; } // Use JCE to decrypt the data using the supplied password. SecretKey skey = getPBEKey(password); Cipher cipher = Cipher.getInstance(algOid.toString()); cipher.init(Cipher.DECRYPT_MODE, skey, algParams); byte[] decryptedPrivateKey = cipher.doFinal(encryptedKey); PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(decryptedPrivateKey); // Parse the key algorithm and then use a JCA key factory to create the private key. DerValue val = new DerValue(decryptedPrivateKey); DerInputStream in = val.toDerInputStream(); // Ignore this -- version should be 0. int i = in.getInteger(); // Get the Algorithm ID next DerValue[] value = in.getSequence(2); AlgorithmId algId = new AlgorithmId(value[0].getOID()); String algName = algId.getName(); // Get a key factory for this algorithm. It's likely to be 'RSA'. KeyFactory kfac = KeyFactory.getInstance(algName); returnValue = kfac.generatePrivate(kspec); } catch (Exception e) { UnrecoverableKeyException uke = new UnrecoverableKeyException("Get Key failed: " + e.getMessage()); uke.initCause(e); throw uke; } return returnValue; }
public void acceptSecContext(InputStream inStream, OutputStream outStream) throws GSSException { if (mechCtxt != null && currentState != IN_PROGRESS) { throw new GSSExceptionImpl(GSSException.FAILURE, "Illegal call to acceptSecContext"); } GSSHeader gssHeader = null; int inTokenLen = -1; GSSCredentialSpi credElement = null; try { if (mechCtxt == null) { // mechOid will be null for an acceptor's context gssHeader = new GSSHeader(inStream); inTokenLen = gssHeader.getMechTokenLength(); /* * Convert ObjectIdentifier to Oid */ objId = gssHeader.getOid(); mechOid = new Oid(objId.toString()); // System.out.println("Entered GSSContextImpl.acceptSecContext" // + " with mechanism = " + mechOid); if (myCred != null) { credElement = myCred.getElement(mechOid, false); } mechCtxt = gssManager.getMechanismContext(credElement, mechOid); mechCtxt.setChannelBinding(channelBindings); currentState = IN_PROGRESS; } else { if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || (GSSUtil.isSpNegoMech(mechOid))) { // do not parse GSS header for native provider and SPNEGO } else { // parse GSS Header gssHeader = new GSSHeader(inStream); if (!gssHeader.getOid().equals((Object) objId)) throw new GSSExceptionImpl( GSSException.DEFECTIVE_TOKEN, "Mechanism not equal to " + mechOid.toString() + " in acceptSecContext token"); inTokenLen = gssHeader.getMechTokenLength(); } } byte[] obuf = mechCtxt.acceptSecContext(inStream, inTokenLen); if (obuf != null) { int retVal = obuf.length; if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || (GSSUtil.isSpNegoMech(mechOid))) { // do not add GSS header for native provider and SPNEGO } else { // add GSS header gssHeader = new GSSHeader(objId, obuf.length); retVal += gssHeader.encode(outStream); } outStream.write(obuf); } if (mechCtxt.isEstablished()) { currentState = READY; } } catch (IOException e) { throw new GSSExceptionImpl(GSSException.DEFECTIVE_TOKEN, e.getMessage()); } }
/* */ static ECParameterSpec getECParameterSpec(ObjectIdentifier paramObjectIdentifier) /* */ { /* 76 */ return getECParameterSpec(paramObjectIdentifier.toString()); /* */ }
/* * Create an OCSP response from its ASN.1 DER encoding. */ OCSPResponse(byte[] bytes) throws IOException { if (dump) { HexDumpEncoder hexEnc = new HexDumpEncoder(); System.out.println("OCSPResponse bytes are..."); System.out.println(hexEnc.encode(bytes)); } 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 = Collections.<X509CertImpl>emptyList(); sigAlgId = null; signature = null; tbsResponseData = null; responseNonce = 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(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 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 short tag = (byte) (seq.tag & 0x1f); if (tag == NAME_TAG) { if (debug != null) { X500Principal responderName = new X500Principal(seq.getData().toByteArray()); debug.println("OCSP Responder name: " + responderName); } } else if (tag == KEY_TAG) { if (debug != null) { byte[] responderKey = seq.getData().getOctetString(); debug.println("OCSP Responder key: " + Debug.toString(responderKey)); } } else { throw new IOException( "Bad encoding in responderID element of " + "OCSP response: expected ASN.1 context specific tag 0 or 1"); } // 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]); singleResponseMap.put(singleResponse.getCertId(), singleResponse); } // responseExtensions byte[] nonce = null; 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((Object) OCSP.NONCE_EXTENSION_OID)) { nonce = ext.getExtensionValue(); } else if (ext.isCritical()) { throw new IOException("Unsupported OCSP critical extension: " + ext.getExtensionId()); } } } } responseNonce = nonce; // 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++) { certs.add(new X509CertImpl(derCerts[i].toByteArray())); } } catch (CertificateException ce) { throw new IOException("Bad encoding in X509 Certificate", ce); } } else { certs = Collections.<X509CertImpl>emptyList(); } }
/** * 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 ResponseStatus[] rsvalues = ResponseStatus.values(); private static final Debug debug = Debug.getInstance("certpath"); private static final boolean dump = false; 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 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(); private final ResponseStatus responseStatus; private final Map<CertId, SingleResponse> singleResponseMap; private final List<X509CertImpl> certs; private final AlgorithmId sigAlgId; private final byte[] signature; private final byte[] tbsResponseData; private final byte[] responseNonce; /* * Create an OCSP response from its ASN.1 DER encoding. */ OCSPResponse(byte[] bytes) throws IOException { if (dump) { HexDumpEncoder hexEnc = new HexDumpEncoder(); System.out.println("OCSPResponse bytes are..."); System.out.println(hexEnc.encode(bytes)); } 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 = Collections.<X509CertImpl>emptyList(); sigAlgId = null; signature = null; tbsResponseData = null; responseNonce = 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(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 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 short tag = (byte) (seq.tag & 0x1f); if (tag == NAME_TAG) { if (debug != null) { X500Principal responderName = new X500Principal(seq.getData().toByteArray()); debug.println("OCSP Responder name: " + responderName); } } else if (tag == KEY_TAG) { if (debug != null) { byte[] responderKey = seq.getData().getOctetString(); debug.println("OCSP Responder key: " + Debug.toString(responderKey)); } } else { throw new IOException( "Bad encoding in responderID element of " + "OCSP response: expected ASN.1 context specific tag 0 or 1"); } // 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]); singleResponseMap.put(singleResponse.getCertId(), singleResponse); } // responseExtensions byte[] nonce = null; 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((Object) OCSP.NONCE_EXTENSION_OID)) { nonce = ext.getExtensionValue(); } else if (ext.isCritical()) { throw new IOException("Unsupported OCSP critical extension: " + ext.getExtensionId()); } } } } responseNonce = nonce; // 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++) { certs.add(new X509CertImpl(derCerts[i].toByteArray())); } } catch (CertificateException ce) { throw new IOException("Bad encoding in X509 Certificate", ce); } } else { certs = Collections.<X509CertImpl>emptyList(); } } void verify(List<CertId> certIds, X509Certificate responderCert, Date date, byte[] nonce) 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()); } } // Check whether the cert returned by the responder is trusted if (!certs.isEmpty()) { X509CertImpl cert = certs.get(0); // First check if the cert matches the expected responder cert if (cert.equals(responderCert)) { // cert is trusted, now verify the signed response // Next check if the cert was issued by the responder cert // which was set locally. } else if (cert.getIssuerX500Principal().equals(responderCert.getSubjectX500Principal())) { // Check for the OCSPSigning key purpose try { List<String> keyPurposes = cert.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(new TrustAnchor(responderCert, null)); algChecker.init(false); algChecker.check(cert, Collections.<String>emptySet()); // check the validity try { if (date == null) { cert.checkValidity(); } else { cert.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 = cert.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 { cert.verify(responderCert.getPublicKey()); responderCert = cert; // cert is trusted, now verify the signed response } catch (GeneralSecurityException e) { responderCert = 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 (responderCert != null) { // Check algorithm constraints specified in security property // "jdk.certpath.disabledAlgorithms". AlgorithmChecker.check(responderCert.getPublicKey(), sigAlgId); if (!verifySignature(responderCert)) { 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"); } // Check freshness of OCSPResponse if (nonce != null) { if (responseNonce != null && !Arrays.equals(nonce, responseNonce)) { throw new CertPathValidatorException("Nonces don't match"); } } 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("Response's validity interval is from " + sr.thisUpdate + until); } // Check that the test date is within the validity interval if ((sr.thisUpdate != null && nowPlusSkew.before(sr.thisUpdate)) || (sr.nextUpdate != null && nowMinusSkew.after(sr.nextUpdate))) { throw new CertPathValidatorException( "Response is unreliable: its validity " + "interval is out-of-date"); } } } /** Returns the OCSP ResponseStatus. */ ResponseStatus getResponseStatus() { return responseStatus; } /* * Verify the signature of the OCSP response. * The responder's cert is implicitly trusted. */ 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. */ SingleResponse getSingleResponse(CertId certId) { return singleResponseMap.get(certId); } /* * A class representing a single OCSP response. */ 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 = 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<String, java.security.cert.Extension>(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(); } } /* * 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<String, java.security.cert.Extension> 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(); } } }