/* * Copyright 2003-2006 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.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.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CertPathValidatorException; import java.security.cert.X509Certificate; import java.security.cert.PKIXParameters; import javax.security.auth.x500.X500Principal; import java.util.Date; import java.util.List; import java.util.Set; import java.util.Iterator; import sun.misc.HexDumpEncoder; 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: * <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 */ class OCSPResponse { // Certificate status CHOICE public static final int CERT_STATUS_GOOD = 0; public static final int CERT_STATUS_REVOKED = 1; public static final int CERT_STATUS_UNKNOWN = 2; private static final Debug DEBUG = Debug.getInstance("certpath"); private static final boolean dump = false; private static final ObjectIdentifier OCSP_BASIC_RESPONSE_OID; private static final ObjectIdentifier OCSP_NONCE_EXTENSION_OID; static { ObjectIdentifier tmp1 = null; ObjectIdentifier tmp2 = null; try { tmp1 = new ObjectIdentifier("1.3.6.1.5.5.7.48.1.1"); tmp2 = new ObjectIdentifier("1.3.6.1.5.5.7.48.1.2"); } catch (Exception e) { // should not happen; log and exit } OCSP_BASIC_RESPONSE_OID = tmp1; OCSP_NONCE_EXTENSION_OID = tmp2; } // OCSP response status code private static final int OCSP_RESPONSE_OK = 0; // 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 SingleResponse singleResponse; /* * Create an OCSP response from its ASN.1 DER encoding. */ // used by OCSPChecker OCSPResponse(byte[] bytes, PKIXParameters params, X509Certificate responderCert) throws IOException, CertPathValidatorException { try { int responseStatus; ObjectIdentifier responseType; int version; CertificateIssuerName responderName = null; Date producedAtDate; AlgorithmId sigAlgId; byte[] ocspNonce; // OCSPResponse 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 responseStatus = derIn.getEnumerated(); if (DEBUG != null) { DEBUG.println("OCSP response: " + responseToText(responseStatus)); } if (responseStatus != OCSP_RESPONSE_OK) { throw new CertPathValidatorException( "OCSP Response Failure: " + responseToText(responseStatus)); } // 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; responseType = derIn.getOID(); if (responseType.equals(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); 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(); 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) { responderName = new CertificateIssuerName(seq.getData()); if (DEBUG != null) { DEBUG.println("OCSP Responder name: " + responderName); } } else if (tag == KEY_TAG) { // Ignore, for now } 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(); producedAtDate = seq.getGeneralizedTime(); // responses DerValue[] singleResponseDer = seqDerIn.getSequence(1); // Examine only the first response singleResponse = new SingleResponse(singleResponseDer[0]); // responseExtensions if (seqDerIn.available() > 0) { seq = seqDerIn.getDerValue(); if (seq.isContextSpecific((byte)1)) { DerValue[] responseExtDer = seq.data.getSequence(3); Extension[] responseExtension = new Extension[responseExtDer.length]; for (int i = 0; i < responseExtDer.length; i++) { responseExtension[i] = new Extension(responseExtDer[i]); if (DEBUG != null) { DEBUG.println("OCSP extension: " + responseExtension[i]); } if ((responseExtension[i].getExtensionId()).equals( OCSP_NONCE_EXTENSION_OID)) { ocspNonce = responseExtension[i].getExtensionValue(); } else if (responseExtension[i].isCritical()) { throw new IOException( "Unsupported OCSP critical extension: " + responseExtension[i].getExtensionId()); } } } } // signatureAlgorithmId 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]; for (int i = 0; i < certs.length; i++) { x509Certs[i] = new X509CertImpl(certs[i].toByteArray()); } } // Check whether the cert returned by the responder is trusted if (x509Certs != null && x509Certs[0] != null) { X509Certificate cert = x509Certs[0]; // First check if the cert matches the responder cert which // was set locally. 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.getIssuerDN().equals( responderCert.getSubjectDN())) { // Check for the OCSPSigning key purpose List<String> keyPurposes = cert.getExtendedKeyUsage(); if (keyPurposes == null || !keyPurposes.contains(KP_OCSP_SIGNING_OID)) { if (DEBUG != null) { DEBUG.println("Responder's certificate is not " + "valid for signing OCSP responses."); } throw new CertPathValidatorException( "Responder's certificate not valid for signing " + "OCSP responses"); } // verify the signature try { cert.verify(responderCert.getPublicKey()); responderCert = cert; // cert is trusted, now verify the signed response } catch (GeneralSecurityException e) { responderCert = null; } } } // Confirm that the signed response was generated using the public // key from the trusted responder cert if (responderCert != null) { if (! verifyResponse(responseDataDer, responderCert, sigAlgId, signature, params)) { if (DEBUG != null) { DEBUG.println("Error verifying OCSP Responder's " + "signature"); } throw new CertPathValidatorException( "Error verifying OCSP Responder's signature"); } } else { // Need responder's cert in order to verify the signature if (DEBUG != null) { DEBUG.println("Unable to verify OCSP Responder's " + "signature"); } throw new CertPathValidatorException( "Unable to verify OCSP Responder's signature"); } } catch (CertPathValidatorException cpve) { throw cpve; } catch (Exception e) { throw new CertPathValidatorException(e); } } /* * 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, PKIXParameters params) throws SignatureException { try { Signature respSignature = Signature.getInstance(sigAlgId.getName()); respSignature.initVerify(cert); 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 SignatureException(ike); } catch (NoSuchAlgorithmException nsae) { throw new SignatureException(nsae); } } /* * Return the revocation status code for a given certificate. */ // used by OCSPChecker int getCertStatus(SerialNumber sn) { // ignore serial number for now; if we support multiple // requests/responses then it will be used return singleResponse.getStatus(); } // used by OCSPChecker CertId getCertId() { return singleResponse.getCertId(); } /* * Map an OCSP response status code to a string. */ static private String responseToText(int status) { switch (status) { case 0: return "Successful"; case 1: return "Malformed request"; case 2: return "Internal error"; case 3: return "Try again later"; case 4: return "Unused status code"; case 5: return "Request must be signed"; case 6: return "Request is unauthorized"; default: return ("Unknown status code: " + status); } } /* * Map a certificate's revocation status code to a string. */ // used by OCSPChecker static String certStatusToText(int certStatus) { switch (certStatus) { case 0: return "Good"; case 1: return "Revoked"; case 2: return "Unknown"; default: return ("Unknown certificate status code: " + certStatus); } } /* * A class representing a single OCSP response. */ private class SingleResponse { private CertId certId; private int certStatus; private Date thisUpdate; private Date nextUpdate; 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_GOOD) { certStatus = CERT_STATUS_GOOD; } else if (tag == CERT_STATUS_REVOKED) { certStatus = CERT_STATUS_REVOKED; // RevokedInfo if (DEBUG != null) { Date revocationTime = derVal.data.getGeneralizedTime(); DEBUG.println("Revocation time: " + revocationTime); } } else if (tag == CERT_STATUS_UNKNOWN) { certStatus = CERT_STATUS_UNKNOWN; } else { throw new IOException("Invalid certificate status"); } thisUpdate = tmp.getGeneralizedTime(); if (tmp.available() == 0) { // we are done } else { derVal = tmp.getDerValue(); tag = (byte)(derVal.tag & 0x1f); if (tag == 0) { // next update nextUpdate = derVal.data.getGeneralizedTime(); if (tmp.available() == 0) { return; } else { derVal = tmp.getDerValue(); tag = (byte)(derVal.tag & 0x1f); } } // ignore extensions } Date now = new Date(); 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 && now.before(thisUpdate)) || (nextUpdate != null && now.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 */ private int getStatus() { return certStatus; } private CertId getCertId() { return certId; } /** * Construct a string representation of a single OCSP response. */ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("SingleResponse: \n"); sb.append(certId); sb.append("\nCertStatus: "+ certStatusToText(getCertStatus(null)) + "\n"); sb.append("thisUpdate is " + thisUpdate + "\n"); if (nextUpdate != null) { sb.append("nextUpdate is " + nextUpdate + "\n"); } return sb.toString(); } } }