blob: 24d88374c1c6c0573a7ff91c8da361c9bbe45fa2 [file] [log] [blame]
/*
* 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.CRLReason;
import java.security.cert.X509Certificate;
import java.security.cert.PKIXParameters;
import javax.security.auth.x500.X500Principal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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;
// 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.
*/
// 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();
}
Date getRevocationTime() {
return singleResponse.getRevocationTime();
}
CRLReason getRevocationReason() {
return singleResponse.getRevocationReason();
}
Map<String, java.security.cert.Extension> getSingleExtensions() {
return singleResponse.getSingleExtensions();
}
/*
* 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 Date revocationTime;
private CRLReason revocationReason = CRLReason.UNSPECIFIED;
private HashMap<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_GOOD) {
certStatus = CERT_STATUS_GOOD;
} else if (tag == CERT_STATUS_REVOKED) {
certStatus = CERT_STATUS_REVOKED;
revocationTime = derVal.data.getGeneralizedTime();
if (derVal.data.available() != 0) {
int reason = derVal.getEnumerated();
// if reason out-of-range just leave as UNSPECIFIED
if (reason >= 0 && reason < values.length) {
revocationReason = values[reason];
}
}
// RevokedInfo
if (DEBUG != null) {
DEBUG.println("Revocation time: " + revocationTime);
DEBUG.println("Revocation reason: " + revocationReason);
}
} 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) {
// we are done
} else {
derVal = tmp.getDerValue();
tag = (byte)(derVal.tag & 0x1f);
}
}
}
// 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]);
singleExtensions.put(ext.getId(), ext);
if (DEBUG != null) {
DEBUG.println("OCSP single extension: " + ext);
}
}
}
}
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;
}
private Date getRevocationTime() {
return revocationTime;
}
private CRLReason getRevocationReason() {
return revocationReason;
}
private Map<String, java.security.cert.Extension> getSingleExtensions() {
return singleExtensions;
}
/**
* 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");
if (certStatus == CERT_STATUS_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();
}
}
}