|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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: |
|
* <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, |
|
MALFORMED_REQUEST, |
|
INTERNAL_ERROR, |
|
TRY_LATER, |
|
UNUSED, |
|
SIG_REQUIRED, |
|
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; |
|
|
|
|
|
private static final int NAME_TAG = 1; |
|
private static final int KEY_TAG = 2; |
|
|
|
|
|
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) |
|
|
|
private static final int DEFAULT_MAX_CLOCK_SKEW = 900000; |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final int MAX_CLOCK_SKEW = initializeClockSkew(); |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
return tmp * 1000; |
|
} |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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(); |
|
|
|
|
|
int status = derIn.getEnumerated(); |
|
if (status >= 0 && status < rsvalues.length) { |
|
responseStatus = rsvalues[status]; |
|
} else { |
|
|
|
throw new IOException("Unknown OCSPResponse status: " + status); |
|
} |
|
if (debug != null) { |
|
debug.println("OCSP response status: " + responseStatus); |
|
} |
|
if (responseStatus != ResponseStatus.SUCCESSFUL) { |
|
|
|
singleResponseMap = Collections.emptyMap(); |
|
certs = new ArrayList<X509CertImpl>(); |
|
sigAlgId = null; |
|
signature = null; |
|
tbsResponseData = null; |
|
responseNonce = null; |
|
responseExtensions = Collections.emptyMap(); |
|
respId = null; |
|
return; |
|
} |
|
|
|
|
|
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."); |
|
} |
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
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]; |
|
|
|
|
|
tbsResponseData = seqTmp[0].toByteArray(); |
|
|
|
|
|
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(); |
|
|
|
|
|
if (seq.isContextSpecific((byte)0)) { |
|
|
|
if (seq.isConstructed() && seq.isContextSpecific()) { |
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
respId = new ResponderId(seq.toByteArray()); |
|
if (debug != null) { |
|
debug.println("Responder ID: " + respId); |
|
} |
|
|
|
|
|
seq = seqDerIn.getDerValue(); |
|
producedAtDate = seq.getGeneralizedTime(); |
|
if (debug != null) { |
|
debug.println("OCSP response produced at: " + producedAtDate); |
|
} |
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
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; |
|
|
|
|
|
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)); |
|
} |
|
|
|
|
|
sigAlgId = AlgorithmId.parse(seqTmp[1]); |
|
|
|
|
|
signature = seqTmp[2].getBitString(); |
|
|
|
|
|
if (seqTmp.length > 3) { |
|
|
|
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 |
|
|
|
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()); |
|
} |
|
} |
|
|
|
|
|
if (signerCert == null) { |
|
// Add the Issuing CA cert and/or Trusted Responder cert to the list |
|
|
|
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 |
|
|
|
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 |
|
|
|
try { |
|
certKeyId = new KeyIdentifier(cert.getPublicKey()); |
|
} catch (IOException e) { |
|
// ignore |
|
} |
|
if (ridKeyId.equals(certKeyId)) { |
|
signerCert = cert; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
if (signerCert != null) { |
|
|
|
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())) { |
|
|
|
|
|
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) { |
|
|
|
throw new CertPathValidatorException( |
|
"Responder's certificate not valid for signing " + |
|
"OCSP responses", cpe); |
|
} |
|
|
|
// Check algorithm constraints specified in security property |
|
|
|
AlgorithmChecker algChecker = |
|
new AlgorithmChecker(issuerInfo.getAnchor(), date, |
|
variant); |
|
algChecker.init(false); |
|
algChecker.check(signerCert, Collections.<String>emptySet()); |
|
|
|
|
|
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. |
|
} |
|
|
|
|
|
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 |
|
|
|
if (signerCert != null) { |
|
// Check algorithm constraints specified in security property |
|
|
|
AlgorithmChecker.check(signerCert.getPublicKey(), sigAlgId, variant); |
|
|
|
if (!verifySignature(signerCert)) { |
|
throw new CertPathValidatorException( |
|
"Error verifying OCSP Response's signature"); |
|
} |
|
} else { |
|
|
|
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"); |
|
} |
|
} |
|
|
|
|
|
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)); |
|
} |
|
|
|
// Check that the test date is within the validity interval: |
|
// [ thisUpdate - 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"); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ResponseStatus getResponseStatus() { |
|
return responseStatus; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public SingleResponse getSingleResponse(CertId certId) { |
|
return singleResponseMap.get(certId); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Set<CertId> getCertIds() { |
|
return Collections.unmodifiableSet(singleResponseMap.keySet()); |
|
} |
|
|
|
|
|
|
|
*/ |
|
X509Certificate getSignerCertificate() { |
|
return signerCert; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ResponderId getResponderId() { |
|
return respId; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
if (ext.isCritical()) { |
|
throw new IOException("Unsupported OCSP critical extension: " + |
|
ext.getExtensionId()); |
|
} |
|
extMap.put(ext.getId(), ext); |
|
} |
|
|
|
return extMap; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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 >= 0 && reason < values.length) { |
|
revocationReason = values[reason]; |
|
} else { |
|
revocationReason = CRLReason.UNSPECIFIED; |
|
} |
|
} else { |
|
revocationReason = CRLReason.UNSPECIFIED; |
|
} |
|
} else { |
|
revocationReason = CRLReason.UNSPECIFIED; |
|
} |
|
|
|
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); |
|
} |
|
|
|
|
|
Date tmpNextUpdate = null; |
|
Map<String, java.security.cert.Extension> tmpMap = null; |
|
|
|
// Check for the first optional item, it could be nextUpdate |
|
|
|
if (tmp.available() > 0) { |
|
derVal = tmp.getDerValue(); |
|
|
|
|
|
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 |
|
|
|
derVal = tmp.available() > 0 ? tmp.getDerValue() : null; |
|
} |
|
|
|
|
|
if (derVal != null) { |
|
if (derVal.isContextSpecific((byte)1)) { |
|
tmpMap = parseExtensions(derVal); |
|
|
|
// There should not be any other items in the |
|
|
|
if (tmp.available() > 0) { |
|
throw new IOException(tmp.available() + |
|
" bytes of additional data in singleResponse"); |
|
} |
|
} else { |
|
|
|
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); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public CertStatus getCertStatus() { |
|
return certStatus; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public CertId getCertId() { |
|
return certId; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getThisUpdate() { |
|
return (thisUpdate != null ? (Date) thisUpdate.clone() : null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getNextUpdate() { |
|
return (nextUpdate != null ? (Date) nextUpdate.clone() : null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public Date getRevocationTime() { |
|
return (revocationTime != null ? (Date) revocationTime.clone() : |
|
null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public CRLReason getRevocationReason() { |
|
return revocationReason; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public Map<String, java.security.cert.Extension> getSingleExtensions() { |
|
return Collections.unmodifiableMap(singleExtensions); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@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(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
X509Certificate getCertificate() { |
|
return certificate; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
X500Principal getName() { |
|
return name; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
PublicKey getPublicKey() { |
|
return pubKey; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
TrustAnchor getAnchor() { |
|
return anchor; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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(); |
|
} |
|
} |
|
} |