Hi everybody,

I have been struggling for some months with a weird issue about how Java
validates OCSP responses. Following the RFC2560 standard the responses
sent by the responder should be signed following one of these three
possibilities:

> All definitive response messages SHALL be digitally signed. The key
>    used to sign the response MUST belong to one of the following:
>
>    -- the CA who issued the certificate in question
>    -- a Trusted Responder whose public key is trusted by the requester
>    -- a CA Designated Responder (Authorized Responder) who holds a
>       specially marked certificate issued directly by the CA, indicating
>       that the responder may issue OCSP responses for that CA

In current java implementation (openjdk 6, 7 and 8) the case (1) and (3)
are considered by default and case (2) can be configured using some
properties ("ocsp.responderCertSubjectName" for example). But the
problem is that both configurations are exclusive, if your application
accepts responses for the cases (1) and (3) it fails with the case (2)
and vice-versa.

I faced an OCSP responder that in some cases it answered using the case
(1) and in others using the case (2). The case (1) was used to sign
responses for their own certificates and the case (2) was used to sign
responses for foreign certificates (spanish national id certificates
specifically). I'm not completely sure if the standard admits this
situation but I haven't read anything against that. Besides why not to
take the configured certificate ("ocsp.responderCertSubjectName" or any
of the other properties) as a failback and not as the unique valid signer.

Looking at the code the problem is that only one certificate is passed
as the valid signer for responses (the one configured via properties or
the issuer cert). Following Andrew advise I have made a little patch
against current openjdk-8 that just considers both of them (OCSPResponse
class receives both certs and this way can check the three cases).

Thanks in advance!

diff --git a/OCSP.java b/OCSP.java
index ca82ef5..d07f01f 100644
--- a/OCSP.java
+++ b/OCSP.java
@@ -129,7 +129,7 @@ public final class OCSP {
                 ("Exception while encoding OCSPRequest", e);
         }
         OCSPResponse ocspResponse = check(Collections.singletonList(certId),
-            responderURI, issuerCert, null, Collections.<Extension>emptyList());
+            responderURI, issuerCert, null, null, Collections.<Extension>emptyList());
         return (RevocationStatus)ocspResponse.getSingleResponse(certId);
     }
 
@@ -176,7 +176,7 @@ public final class OCSP {
                 ("Exception while encoding OCSPRequest", e);
         }
         OCSPResponse ocspResponse = check(Collections.singletonList(certId),
-            responderURI, responderCert, date, extensions);
+            responderURI, issuerCert, responderCert, date, extensions);
         return (RevocationStatus) ocspResponse.getSingleResponse(certId);
     }
 
@@ -185,6 +185,7 @@ public final class OCSP {
      *
      * @param certs the CertIds to be checked
      * @param responderURI the URI of the OCSP responder
+     * @param issuerCert the issuer certificate
      * @param responderCert the OCSP responder's certificate
      * @param date the time the validity of the OCSP responder's certificate
      *    should be checked against. If null, the current time is used.
@@ -195,7 +196,7 @@ public final class OCSP {
      *    encoding the OCSP Request or validating the OCSP Response
      */
     static OCSPResponse check(List<CertId> certIds, URI responderURI,
-                              X509Certificate responderCert, Date date,
+                              X509Certificate issuerCert, X509Certificate responderCert, Date date,
                               List<Extension> extensions)
         throws IOException, CertPathValidatorException
     {
@@ -282,7 +283,7 @@ public final class OCSP {
         }
 
         // verify the response
-        ocspResponse.verify(certIds, responderCert, date, request.getNonce());
+        ocspResponse.verify(certIds, issuerCert, responderCert, date, request.getNonce());
 
         return ocspResponse;
     }
diff --git a/OCSPResponse.java b/OCSPResponse.java
index 2cd437d..193cfc0 100644
--- a/OCSPResponse.java
+++ b/OCSPResponse.java
@@ -377,7 +377,7 @@ public final class OCSPResponse {
         }
     }
 
-    void verify(List<CertId> certIds, X509Certificate responderCert,
+    void verify(List<CertId> certIds, X509Certificate issuerCert, X509Certificate responderCert,
                 Date date, byte[] nonce)
         throws CertPathValidatorException
     {
@@ -411,11 +411,30 @@ public final class OCSPResponse {
             // First check if the cert matches the expected responder cert
             if (cert.equals(responderCert)) {
                 // cert is trusted, now verify the signed response
+                if (debug != null) {
+                    debug.println("Case (2): Trusted Responder whose public key is trusted by the requester");
+                }
+
+            // Next check if it is the issuerCert (common situation)
+            } else if (cert.equals(issuerCert)) {
+                if (debug != null) {
+                    debug.println("Case (1): CA who issued the certificate in question");
+                }
+                responderCert = issuerCert;
 
             // Next check if the cert was issued by the responder cert
             // which was set locally.
-            } else if (cert.getIssuerX500Principal().equals(
-                       responderCert.getSubjectX500Principal())) {
+            } else if (cert.getIssuerX500Principal().equals(issuerCert.getSubjectX500Principal()) ||
+                    (responderCert != null && 
+                     cert.getIssuerX500Principal().equals(responderCert.getSubjectX500Principal()))) {
+                // the case of responderCert is not covered by the RFC2560
+                // but it was covered before, so I won't change the behavior
+                if (cert.getIssuerX500Principal().equals(issuerCert.getSubjectX500Principal())) {
+                    responderCert = issuerCert;
+                }
+                if (debug != null) {
+                    debug.println("Case (3): CA Designated Responder (Authorized Responder) who holds a specially marked certificate issued directly by the CA, indicating that the responder may issue OCSP responses for that CA");
+                }
 
                 // Check for the OCSPSigning key purpose
                 try {
@@ -537,6 +556,18 @@ public final class OCSPResponse {
     }
 
     /**
+     * Returns the signer certificate if it exists.
+     * @return The first certificate used to sign the response or null
+     */
+    X509CertImpl getSignerCertificate() {
+      if (!certs.isEmpty()) {
+          return certs.get(0);
+      } else {
+          return null;
+      }
+    }
+
+    /**
      * Returns the OCSP ResponseStatus.
      */
     ResponseStatus getResponseStatus() {
diff --git a/RevocationChecker.java b/RevocationChecker.java
index 98d8a9d..d1e50bd 100644
--- a/RevocationChecker.java
+++ b/RevocationChecker.java
@@ -633,9 +633,6 @@ class RevocationChecker extends PKIXRevocationChecker {
         URI responderURI = (this.responderURI != null)
                            ? this.responderURI : getOCSPServerURI(currCert);
 
-        X509Certificate respCert = (responderCert == null) ? issuerCert
-                                                           : responderCert;
-
         // The algorithm constraints of the OCSP trusted responder certificate
         // does not need to be checked in this code. The constraints will be
         // checked when the responder's certificate is validated.
@@ -667,12 +664,12 @@ class RevocationChecker extends PKIXRevocationChecker {
                         nonce = ext.getValue();
                     }
                 }
-                response.verify(Collections.singletonList(certId), respCert,
+                response.verify(Collections.singletonList(certId), issuerCert, responderCert,
                                 params.date(), nonce);
 
             } else {
                 response = OCSP.check(Collections.singletonList(certId),
-                                      responderURI, respCert, params.date(),
+                                      responderURI, issuerCert, responderCert, params.date(),
                                       ocspExtensions);
             }
         } catch (IOException e) {
@@ -685,7 +682,7 @@ class RevocationChecker extends PKIXRevocationChecker {
         if (certStatus == RevocationStatus.CertStatus.REVOKED) {
             Throwable t = new CertificateRevokedException(
                 rs.getRevocationTime(), rs.getRevocationReason(),
-                respCert.getSubjectX500Principal(), rs.getSingleExtensions());
+                response.getSignerCertificate().getSubjectX500Principal(), rs.getSingleExtensions());
             throw new CertPathValidatorException(t.getMessage(), t, null,
                                                  -1, BasicReason.REVOKED);
         } else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) {

Reply via email to