8132926: PKIXParameters built with public key form of TrustAnchor causes NPE during cert path building/validation
authorjnimeh
Wed, 14 Sep 2016 07:37:15 -0700
changeset 40946 362ab0ff2d9a
parent 40945 f241705723ea
child 40947 16ef1ab3108e
8132926: PKIXParameters built with public key form of TrustAnchor causes NPE during cert path building/validation Summary: Fix cases where non-certificate issuer information may be passed into the OCSPResponse.verify method, thereby causing NPEs to be thrown. Reviewed-by: xuelei
jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSP.java
jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPResponse.java
jdk/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java
jdk/test/java/security/cert/CertPathValidator/trustAnchor/ValWithAnchorByName.java
--- a/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSP.java	Wed Sep 14 06:46:19 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSP.java	Wed Sep 14 07:37:15 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2016, Oracle and/or its affiliates. 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
@@ -125,7 +125,7 @@
                 ("Exception while encoding OCSPRequest", e);
         }
         OCSPResponse ocspResponse = check(Collections.singletonList(certId),
-            responderURI, issuerCert, null, null,
+            responderURI, new OCSPResponse.IssuerInfo(issuerCert), null, null,
             Collections.<Extension>emptyList());
         return (RevocationStatus)ocspResponse.getSingleResponse(certId);
     }
@@ -173,7 +173,8 @@
                 ("Exception while encoding OCSPRequest", e);
         }
         OCSPResponse ocspResponse = check(Collections.singletonList(certId),
-            responderURI, issuerCert, responderCert, date, extensions);
+            responderURI, new OCSPResponse.IssuerInfo(issuerCert),
+            responderCert, date, extensions);
         return (RevocationStatus) ocspResponse.getSingleResponse(certId);
     }
 
@@ -182,7 +183,7 @@
      *
      * @param certIds the CertIds to be checked
      * @param responderURI the URI of the OCSP responder
-     * @param issuerCert the issuer's certificate
+     * @param issuerInfo the issuer's certificate and/or subject and public key
      * @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,8 +196,8 @@
      * @throws CertPathValidatorException if an exception occurs while
      *    encoding the OCSP Request or validating the OCSP Response
      */
-    static OCSPResponse check(List<CertId> certIds, URI responderURI,
-                              X509Certificate issuerCert,
+        static OCSPResponse check(List<CertId> certIds, URI responderURI,
+                              OCSPResponse.IssuerInfo issuerInfo,
                               X509Certificate responderCert, Date date,
                               List<Extension> extensions)
         throws IOException, CertPathValidatorException
@@ -214,7 +215,7 @@
             ocspResponse = new OCSPResponse(response);
 
             // verify the response
-            ocspResponse.verify(certIds, issuerCert, responderCert, date,
+            ocspResponse.verify(certIds, issuerInfo, responderCert, date,
                     nonce);
         } catch (IOException ioe) {
             throw new CertPathValidatorException(
--- a/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPResponse.java	Wed Sep 14 06:46:19 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/OCSPResponse.java	Wed Sep 14 07:37:15 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2016, Oracle and/or its affiliates. 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
@@ -41,6 +41,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import javax.security.auth.x500.X500Principal;
 
@@ -373,8 +374,8 @@
         }
     }
 
-    void verify(List<CertId> certIds, X509Certificate issuerCert,
-                X509Certificate responderCert, Date date, byte[] nonce)
+    void verify(List<CertId> certIds, IssuerInfo issuerInfo,
+            X509Certificate responderCert, Date date, byte[] nonce)
         throws CertPathValidatorException
     {
         switch (responseStatus) {
@@ -414,7 +415,9 @@
             // Add the Issuing CA cert and/or Trusted Responder cert to the list
             // of certs from the OCSP response
             try {
-                certs.add(X509CertImpl.toImpl(issuerCert));
+                if (issuerInfo.getCertificate() != null) {
+                    certs.add(X509CertImpl.toImpl(issuerInfo.getCertificate()));
+                }
                 if (responderCert != null) {
                     certs.add(X509CertImpl.toImpl(responderCert));
                 }
@@ -464,7 +467,10 @@
         // Check whether the signer cert returned by the responder is trusted
         if (signerCert != null) {
             // Check if the response is signed by the issuing CA
-            if (signerCert.equals(issuerCert)) {
+            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");
@@ -481,7 +487,7 @@
 
             // Check if the response is signed by an authorized responder
             } else if (signerCert.getIssuerX500Principal().equals(
-                       issuerCert.getSubjectX500Principal())) {
+                    issuerInfo.getName())) {
 
                 // Check for the OCSPSigning key purpose
                 try {
@@ -502,7 +508,8 @@
                 // Check algorithm constraints specified in security property
                 // "jdk.certpath.disabledAlgorithms".
                 AlgorithmChecker algChecker = new AlgorithmChecker(
-                                    new TrustAnchor(issuerCert, null));
+                        new TrustAnchor(issuerInfo.getName(),
+                                issuerInfo.getPublicKey(), null));
                 algChecker.init(false);
                 algChecker.check(signerCert, Collections.<String>emptySet());
 
@@ -540,7 +547,7 @@
 
                 // verify the signature
                 try {
-                    signerCert.verify(issuerCert.getPublicKey());
+                    signerCert.verify(issuerInfo.getPublicKey());
                     if (debug != null) {
                         debug.println("OCSP response is signed by an " +
                             "Authorized Responder");
@@ -971,4 +978,86 @@
             return sb.toString();
         }
     }
+
+    /**
+     * Helper class that allows consumers to pass in issuer information.  This
+     * will always consist of the issuer's name and public key, but may also
+     * contain a certificate if the originating data is in that form.
+     */
+    static final class IssuerInfo {
+        private final X509Certificate certificate;
+        private final X500Principal name;
+        private final PublicKey pubKey;
+
+        IssuerInfo(X509Certificate issuerCert) {
+            certificate = Objects.requireNonNull(issuerCert,
+                    "Constructor requires non-null certificate");
+            name = certificate.getSubjectX500Principal();
+            pubKey = certificate.getPublicKey();
+        }
+
+        IssuerInfo(X500Principal subjectName, PublicKey key) {
+            certificate = null;
+            name = Objects.requireNonNull(subjectName,
+                    "Constructor requires non-null subject");
+            pubKey = Objects.requireNonNull(key,
+                    "Constructor requires non-null public key");
+        }
+
+        IssuerInfo(TrustAnchor anchor) {
+            certificate = anchor.getTrustedCert();
+            if (certificate != null) {
+                name = certificate.getSubjectX500Principal();
+                pubKey = certificate.getPublicKey();
+            } else {
+                name = anchor.getCA();
+                pubKey = anchor.getCAPublicKey();
+            }
+        }
+
+        /**
+         * Get the certificate in this IssuerInfo if present.
+         *
+         * @return the {@code X509Certificate} used to create this IssuerInfo
+         * object, or {@code null} if a certificate was not used in its
+         * creation.
+         */
+        X509Certificate getCertificate() {
+            return certificate;
+        }
+
+        /**
+         * Get the name of this issuer.
+         *
+         * @return an {@code X500Principal} corresponding to this issuer's
+         * name.  If derived from an issuer's {@code X509Certificate} this
+         * would be equivalent to the certificate subject name.
+         */
+        X500Principal getName() {
+            return name;
+        }
+
+        /**
+         * Get the public key for this issuer.
+         *
+         * @return a {@code PublicKey} for this issuer.
+         */
+        PublicKey getPublicKey() {
+            return pubKey;
+        }
+
+        /**
+         * Create a string representation of this IssuerInfo.
+         *
+         * @return a {@code String} form of this IssuerInfo object.
+         */
+        @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();
+        }
+    }
 }
--- a/jdk/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java	Wed Sep 14 06:46:19 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java	Wed Sep 14 07:37:15 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2016, Oracle and/or its affiliates. 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
@@ -61,12 +61,12 @@
     private List<CertStore> certStores;
     private Map<X509Certificate, byte[]> ocspResponses;
     private List<Extension> ocspExtensions;
-    private boolean legacy;
+    private final boolean legacy;
     private LinkedList<CertPathValidatorException> softFailExceptions =
         new LinkedList<>();
 
     // state variables
-    private X509Certificate issuerCert;
+    private OCSPResponse.IssuerInfo issuerInfo;
     private PublicKey prevPubKey;
     private boolean crlSignFlag;
     private int certIndex;
@@ -301,9 +301,9 @@
                 CertPathValidatorException("forward checking not supported");
         }
         if (anchor != null) {
-            issuerCert = anchor.getTrustedCert();
-            prevPubKey = (issuerCert != null) ? issuerCert.getPublicKey()
-                                              : anchor.getCAPublicKey();
+            issuerInfo = new OCSPResponse.IssuerInfo(anchor);
+            prevPubKey = issuerInfo.getPublicKey();
+
         }
         crlSignFlag = true;
         if (params != null && params.certPath() != null) {
@@ -437,7 +437,7 @@
     private void updateState(X509Certificate cert)
         throws CertPathValidatorException
     {
-        issuerCert = cert;
+        issuerInfo = new OCSPResponse.IssuerInfo(cert);
 
         // Make new public key if parameters are missing
         PublicKey pubKey = cert.getPublicKey();
@@ -708,14 +708,8 @@
         OCSPResponse response = null;
         CertId certId = null;
         try {
-            if (issuerCert != null) {
-                certId = new CertId(issuerCert,
-                                    currCert.getSerialNumberObject());
-            } else {
-                // must be an anchor name and key
-                certId = new CertId(anchor.getCA(), anchor.getCAPublicKey(),
-                                    currCert.getSerialNumberObject());
-            }
+            certId = new CertId(issuerInfo.getName(), issuerInfo.getPublicKey(),
+                    currCert.getSerialNumberObject());
 
             // check if there is a cached OCSP response available
             byte[] responseBytes = ocspResponses.get(cert);
@@ -732,8 +726,8 @@
                         nonce = ext.getValue();
                     }
                 }
-                response.verify(Collections.singletonList(certId), issuerCert,
-                                responderCert, params.date(), nonce);
+                response.verify(Collections.singletonList(certId), issuerInfo,
+                        responderCert, params.date(), nonce);
 
             } else {
                 URI responderURI = (this.responderURI != null)
@@ -746,8 +740,8 @@
                 }
 
                 response = OCSP.check(Collections.singletonList(certId),
-                                      responderURI, issuerCert, responderCert,
-                                      null, ocspExtensions);
+                                      responderURI, issuerInfo,
+                                      responderCert, null, ocspExtensions);
             }
         } catch (IOException e) {
             throw new CertPathValidatorException(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/security/cert/CertPathValidator/trustAnchor/ValWithAnchorByName.java	Wed Sep 14 07:37:15 2016 -0700
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2004, 2016, Oracle and/or its affiliates. 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.
+ *
+ * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8132926
+ * @summary PKIXParameters built with public key form of TrustAnchor causes
+ *          NPE during cert path building/validation
+ * @run main ValWithAnchorByName
+ */
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.PKIXParameters;
+import java.security.cert.PKIXRevocationChecker;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+// To get certpath debugging, add -Djava.security.debug=certpath
+
+public class ValWithAnchorByName {
+
+    // The following certificates and OCSP responses were captured from
+    // a test run that used certificates and responses generated by
+    // sun.security.testlibrary.CertificateBuilder and
+    // sun.security.testlibrary.SimpleOCSPServer.
+
+    // Subject: CN=SSLCertificate, O=SomeCompany
+    // Issuer: CN=Intermediate CA Cert, O=SomeCompany
+    // Validity: Tue Aug 30 14:37:19 PDT 2016 to Wed Aug 30 14:37:19 PDT 2017
+    private static final String EE_CERT =
+        "-----BEGIN CERTIFICATE-----\n" +
+        "MIIDnTCCAoWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwNTEUMBIGA1UEChMLU29t\n" +
+        "ZUNvbXBhbnkxHTAbBgNVBAMTFEludGVybWVkaWF0ZSBDQSBDZXJ0MB4XDTE2MDgz\n" +
+        "MDIxMzcxOVoXDTE3MDgzMDIxMzcxOVowLzEUMBIGA1UEChMLU29tZUNvbXBhbnkx\n" +
+        "FzAVBgNVBAMTDlNTTENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+        "MIIBCgKCAQEAjgv8KKE4CO0rbCjRLA1hXjRiSq30jeusCJ8frbRG+QOBgQ3j6jgc\n" +
+        "vk5wG1aTu7R4AFn0/HRDMzP9ZbRlZVIbJUTd8YiaNyZeyWapPnxHWrPCd5e1xopk\n" +
+        "ElieDdEH5FiLGtIrWy56CGA1hfQb1vUVYegyeY+TTtMFVHt0PrmMk4ZRgj/GtVNp\n" +
+        "BQQYIzaYAcrcWMeCn30ZrhaGAL1hsdgmEVV1wsTD4JeNMSwLwMYem7fg8ondGZIR\n" +
+        "kZuGtuSdOHu4Xz+mgDNXTeX/Bp/dQFucxCG+FOOM9Hoz72RY2W8YqgL38RlnwYWp\n" +
+        "nUNxhXWFH6vyINRQVEu3IgahR6HXjxM7LwIDAQABo4G8MIG5MBQGA1UdEQQNMAuC\n" +
+        "CWxvY2FsaG9zdDAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9s\n" +
+        "b2NhbGhvc3Q6NDIzMzMwHwYDVR0jBBgwFoAUYT525lwHCI4CmuWs8a7poaeKRJ4w\n" +
+        "HQYDVR0OBBYEFCaQnOX4L1ovqyfeKuoay+kI+lXgMA4GA1UdDwEB/wQEAwIFoDAd\n" +
+        "BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEB\n" +
+        "AD8dqQIqFasJcL8lm4mPTsBl0JgNiN8tQcXM7VCvcH+yDvEyh9vudDjuhpSORqPq\n" +
+        "f1o/EvJ+gfs269mBnYQujYRvmSd6EAcBntv5zn6amOh03o6PqTY9KaUC/mL9hB84\n" +
+        "Y5/LYioP16sME7egKnlrGUgKh0ZvGzm7c3SYx3Z5YoeFBOkZajc7Jm+cBw/uBQkF\n" +
+        "a9mLEczIvOgkq1wto8vr2ptH1gEuvFRcorN3muvq34bk40G08+AHlP3fCLFpI3FA\n" +
+        "IStJLJZRcO+Ib4sOcKuaBGnuMo/QVOCEMDUs6RgiWtSd93OZKFIUOASVp6YIkcSs\n" +
+        "5/rmc06sICqBjLfPEB68Jjw=\n" +
+        "-----END CERTIFICATE-----";
+
+    // Subject: CN=Intermediate CA Cert, O=SomeCompany
+    // Issuer: CN=Root CA Cert, O=SomeCompany
+    // Validity: Sun Aug 07 14:37:19 PDT 2016 to Tue Aug 07 14:37:19 PDT 2018
+    private static final String INT_CA_CERT =
+        "-----BEGIN CERTIFICATE-----\n" +
+        "MIIDdjCCAl6gAwIBAgIBZDANBgkqhkiG9w0BAQsFADAtMRQwEgYDVQQKEwtTb21l\n" +
+        "Q29tcGFueTEVMBMGA1UEAxMMUm9vdCBDQSBDZXJ0MB4XDTE2MDgwNzIxMzcxOVoX\n" +
+        "DTE4MDgwNzIxMzcxOVowNTEUMBIGA1UEChMLU29tZUNvbXBhbnkxHTAbBgNVBAMT\n" +
+        "FEludGVybWVkaWF0ZSBDQSBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+        "CgKCAQEAnJR5CnE7GKlQjigExSJ6hHu302mc0PcA6TDgsIitPYD/r8RBbBuE51OQ\n" +
+        "7IP7AXmfPUV3/+pO/uxx6mgY5O6XeUl7KadhVPtPcL0BVVevCSOdTMVa3iV4zRpa\n" +
+        "C6Uy2ouUFnafKnDtlbieggyETUoNgVNJYA9L0XNhtSnENoLHC4Pq0v8OsNtsOWFR\n" +
+        "NiMTOA49NNDBw85WgPyFAxjqO4z0J0zxdWq3W4rSMB8xrkulv2Rvj3GcfYJK/ab8\n" +
+        "V1IJ6PMWCpujASY3BzvYPnN7BKuBjbWJPgZdPYfX1cxeG80u0tOuMfWWiNONSMSA\n" +
+        "7m9y304QA0gKqlrFFn9U4hU89kv1IwIDAQABo4GYMIGVMA8GA1UdEwEB/wQFMAMB\n" +
+        "Af8wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzABhhZodHRwOi8vbG9jYWxob3N0\n" +
+        "OjM5MTM0MB8GA1UdIwQYMBaAFJNMsejEyJUB9tiWycVczvpiMVQZMB0GA1UdDgQW\n" +
+        "BBRhPnbmXAcIjgKa5azxrumhp4pEnjAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcN\n" +
+        "AQELBQADggEBAE4nOFdW9OirPnRvxihQXYL9CXLuGQz5tr0XgN8wSY6Un9b6CRiK\n" +
+        "7obgIGimVdhvUC1qdRcwJqgOfJ2/jR5/5Qo0TVp+ww4dHNdUoj73tagJ7jTu0ZMz\n" +
+        "5Zdp0uwd4RD/syvTeVcbPc3m4awtgEvRgzpDMcSeKPZWInlo7fbnowKSAUAfO8de\n" +
+        "0cDkxEBkzPIzGNu256cdLZOqOK9wLJ9mQ0zKgi/2NsldNc2pl/6jkGpA6uL5lJsm\n" +
+        "fo9sDusWNHV1YggqjDQ19hrf40VuuC9GFl/qAW3marMuEzY/NiKVUxty1q1s48SO\n" +
+        "g5LoEPDDkbygOt7ICL3HYG1VufhC1Q2YY9c=\n" +
+        "-----END CERTIFICATE-----";
+
+    // Subject: CN=Root CA Cert, O=SomeCompany
+    // Issuer: CN=Root CA Cert, O=SomeCompany
+    // Validity: Fri Jul 08 14:37:18 PDT 2016 to Fri Jun 28 14:37:18 PDT 2019
+    private static final String ROOT_CA_CERT =
+        "-----BEGIN CERTIFICATE-----\n" +
+        "MIIDODCCAiCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAtMRQwEgYDVQQKEwtTb21l\n" +
+        "Q29tcGFueTEVMBMGA1UEAxMMUm9vdCBDQSBDZXJ0MB4XDTE2MDcwODIxMzcxOFoX\n" +
+        "DTE5MDYyODIxMzcxOFowLTEUMBIGA1UEChMLU29tZUNvbXBhbnkxFTATBgNVBAMT\n" +
+        "DFJvb3QgQ0EgQ2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIlN\n" +
+        "M3WYEqkU2elXEZrV9QSDbDKwyaLEHafLFciH8Edoag3q/7jEzFJxI7JZ831tdbWQ\n" +
+        "Bm6Hgo+8pvetOFW1BckL8eIjyOONP2CKfFaeMaozsWi1cgxa+rjpU/Rekc+zBqvv\n" +
+        "y4Sr97TwT6nQiLlgjC1nCfR1SVpO51qoDChS7n785rsKEZxw/p+kkVWSZffU7zN9\n" +
+        "c645cPg//L/kjiyeKMkaquGQOYS68gQgy8YZXQv1E3l/8e8Ci1s1DYA5wpCbaBqg\n" +
+        "Tw84Rr4zlUEQBgXzQlRt+mPzeaDpdG1EeGkXrcdkZ+0EMELoOVXOEn6VNsz6vT3I\n" +
+        "KrnvQBSnN06xq/iWwC0CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME\n" +
+        "GDAWgBSTTLHoxMiVAfbYlsnFXM76YjFUGTAdBgNVHQ4EFgQUk0yx6MTIlQH22JbJ\n" +
+        "xVzO+mIxVBkwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQAAi+Nl\n" +
+        "sxP9t2IhiZIHRJGSBZuQlXIjwYIwbq3ZWc/ApZ+0oxtl7DYQi5uRNt8/opcGNCHc\n" +
+        "IY0fG93SbkDubXbxPYBW6D/RUjbz59ZryaP5ym55p1MjHTOqy+AM8g41xNTJikc3\n" +
+        "UUFXXnckeFbawijCsb7vf71owzKuxgBXi9n1rmXXtncKoA/LrUVXoUlKefdgDnsU\n" +
+        "sl3Q29eibE3HSqziMMoAOLm0jjekFGWIgLeTtyRYR1d0dNaUwsHTrQpPjxxUTn1x\n" +
+        "sAPpXKfzPnsYAZeeiaaE75GwbWlHzrNinvxdZQd0zctpfBJfVqD/+lWANlw+rOaK\n" +
+        "J2GyCaJINsyaI/I2\n" +
+        "-----END CERTIFICATE-----";
+
+    // OCSP Response Status: successful (0x0)
+    // Response Type: Basic OCSP Response
+    // Version: 1 (0x0)
+    // Responder Id: CN=Intermediate CA Cert, O=SomeCompany
+    // Produced At: Sep  6 21:37:20 2016 GMT
+    // Responses:
+    // Certificate ID:
+    //    Hash Algorithm: sha1
+    //    Issuer Name Hash: 7ED23D4396152EAB7D0C4AD8C1CA1418AA05DD54
+    //    Issuer Key Hash: 613E76E65C07088E029AE5ACF1AEE9A1A78A449E
+    //    Serial Number: 1000
+    // Cert Status: good
+    // This Update: Sep  6 21:37:20 2016 GMT
+    // Next Update: Sep  6 22:37:19 2016 GMT
+    private static final String EE_OCSP_RESP =
+        "MIIFbAoBAKCCBWUwggVhBgkrBgEFBQcwAQEEggVSMIIFTjCBtaE3MDUxFDASBgNV\n" +
+        "BAoTC1NvbWVDb21wYW55MR0wGwYDVQQDExRJbnRlcm1lZGlhdGUgQ0EgQ2VydBgP\n" +
+        "MjAxNjA5MDYyMTM3MjBaMGUwYzA7MAkGBSsOAwIaBQAEFH7SPUOWFS6rfQxK2MHK\n" +
+        "FBiqBd1UBBRhPnbmXAcIjgKa5azxrumhp4pEngICEACAABgPMjAxNjA5MDYyMTM3\n" +
+        "MjBaoBEYDzIwMTYwOTA2MjIzNzE5WqECMAAwDQYJKoZIhvcNAQELBQADggEBAF13\n" +
+        "cLwxDG8UYPIbzID86vZGOWUuv5c35VnvebMk/ajAUdpItDYshIQVi90Z8BB2TEi/\n" +
+        "wtx1aNkIv7db0uQ0NnRfvME8vG2PWbty36CNAYr/M5UVzUmELH2sGTyf2fKfNIUK\n" +
+        "Iya/NRxCqxLAc34NYH0YyGJ9VcDjbEMNSBAHIqDdBNqKUPnjn454yoivU2oEs294\n" +
+        "cGePMx3QLyPepMwUss8nW74yIF7vxfJ+KFDBGWNuZDRfXScsGIoeM0Vt9B+4fmnV\n" +
+        "nP4Dw6l3IwmQH4ppjg08qTKvyrXcF2dPDWa98Xw6bA5G085Z/b/6/6GpkvKx/q6i\n" +
+        "UqKwF7q5hkDcB+N4/5SgggN+MIIDejCCA3YwggJeoAMCAQICAWQwDQYJKoZIhvcN\n" +
+        "AQELBQAwLTEUMBIGA1UEChMLU29tZUNvbXBhbnkxFTATBgNVBAMTDFJvb3QgQ0Eg\n" +
+        "Q2VydDAeFw0xNjA4MDcyMTM3MTlaFw0xODA4MDcyMTM3MTlaMDUxFDASBgNVBAoT\n" +
+        "C1NvbWVDb21wYW55MR0wGwYDVQQDExRJbnRlcm1lZGlhdGUgQ0EgQ2VydDCCASIw\n" +
+        "DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJyUeQpxOxipUI4oBMUieoR7t9Np\n" +
+        "nND3AOkw4LCIrT2A/6/EQWwbhOdTkOyD+wF5nz1Fd//qTv7scepoGOTul3lJeymn\n" +
+        "YVT7T3C9AVVXrwkjnUzFWt4leM0aWgulMtqLlBZ2nypw7ZW4noIMhE1KDYFTSWAP\n" +
+        "S9FzYbUpxDaCxwuD6tL/DrDbbDlhUTYjEzgOPTTQwcPOVoD8hQMY6juM9CdM8XVq\n" +
+        "t1uK0jAfMa5Lpb9kb49xnH2CSv2m/FdSCejzFgqbowEmNwc72D5zewSrgY21iT4G\n" +
+        "XT2H19XMXhvNLtLTrjH1lojTjUjEgO5vct9OEANICqpaxRZ/VOIVPPZL9SMCAwEA\n" +
+        "AaOBmDCBlTAPBgNVHRMBAf8EBTADAQH/MDIGCCsGAQUFBwEBBCYwJDAiBggrBgEF\n" +
+        "BQcwAYYWaHR0cDovL2xvY2FsaG9zdDozOTEzNDAfBgNVHSMEGDAWgBSTTLHoxMiV\n" +
+        "AfbYlsnFXM76YjFUGTAdBgNVHQ4EFgQUYT525lwHCI4CmuWs8a7poaeKRJ4wDgYD\n" +
+        "VR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQBOJzhXVvToqz50b8YoUF2C\n" +
+        "/Qly7hkM+ba9F4DfMEmOlJ/W+gkYiu6G4CBoplXYb1AtanUXMCaoDnydv40ef+UK\n" +
+        "NE1afsMOHRzXVKI+97WoCe407tGTM+WXadLsHeEQ/7Mr03lXGz3N5uGsLYBL0YM6\n" +
+        "QzHEnij2ViJ5aO3256MCkgFAHzvHXtHA5MRAZMzyMxjbtuenHS2TqjivcCyfZkNM\n" +
+        "yoIv9jbJXTXNqZf+o5BqQOri+ZSbJn6PbA7rFjR1dWIIKow0NfYa3+NFbrgvRhZf\n" +
+        "6gFt5mqzLhM2PzYilVMbctatbOPEjoOS6BDww5G8oDreyAi9x2BtVbn4QtUNmGPX";
+
+    // OCSP Response Status: successful (0x0)
+    // Response Type: Basic OCSP Response
+    // Version: 1 (0x0)
+    // Responder Id: O = SomeCompany, CN = Root CA Cert
+    // Produced At: Sep  6 21:37:20 2016 GMT
+    // Responses:
+    // Certificate ID:
+    //   Hash Algorithm: sha1
+    //   Issuer Name Hash: C8ED9F4E9AC0052A978257C569E6A7C9C45F5CB5
+    //   Issuer Key Hash: 934CB1E8C4C89501F6D896C9C55CCEFA62315419
+    //   Serial Number: 64
+    // Cert Status: good
+    // This Update: Sep  6 21:37:20 2016 GMT
+    // Next Update: Sep  6 22:37:19 2016 GMT
+    private static final String INT_CA_OCSP_RESP =
+        "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\n" +
+        "X1ag//pVgDZcPqzmiidhsgmiSDbMmiPyNg==";
+
+    // Do path validation as if it is always Tue, 06 Sep 2016 22:12:21 GMT
+    // This value is within the lifetimes of all certificates and both OCSP
+    // responses.
+    private static final Date EVAL_DATE = new Date(1473199941000L);
+
+    private static final Base64.Decoder DECODER = Base64.getMimeDecoder();
+
+    public static void main(String[] args) throws Exception {
+        TrustAnchor anchor;
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate rootCert = generateCertificate(cf, ROOT_CA_CERT);
+        X509Certificate eeCert = generateCertificate(cf, EE_CERT);
+        X509Certificate intCaCert = generateCertificate(cf, INT_CA_CERT);
+        List<X509Certificate> certList = new ArrayList<X509Certificate>() {{
+            add(eeCert);
+            add(intCaCert);
+        }};
+
+        System.out.println("==== Certificate Path =====");
+        for (X509Certificate c : certList) {
+            System.out.println(c + "\n");
+        }
+        System.out.println("===========================");
+
+        System.out.println("===== Test 1: TA(X509Certificate) =====");
+        anchor = new TrustAnchor(rootCert, null);
+        runTest(cf, certList, anchor);
+
+        System.out.println("===== Test 2: TA(X500Principal, PublicKey =====");
+        anchor = new TrustAnchor(rootCert.getSubjectX500Principal(),
+                rootCert.getPublicKey(), null);
+        runTest(cf, certList, anchor);
+
+        System.out.println("===== Test 3: TA(String, PublicKey =====");
+        anchor = new TrustAnchor(rootCert.getSubjectX500Principal().getName(),
+                rootCert.getPublicKey(), null);
+        runTest(cf, certList, anchor);
+    }
+
+    private static void runTest(CertificateFactory cf,
+            List<X509Certificate> certList, TrustAnchor anchor)
+            throws Exception {
+        CertPath path = cf.generateCertPath(certList);
+        CertPathValidator validator = CertPathValidator.getInstance("PKIX");
+
+        System.out.println(anchor);
+
+        // Attach the OCSP responses to a PKIXParameters object
+        PKIXRevocationChecker pkrev =
+                (PKIXRevocationChecker)validator.getRevocationChecker();
+        Map<X509Certificate, byte[]> responseMap = new HashMap<>();
+        responseMap.put(certList.get(0), DECODER.decode(EE_OCSP_RESP));
+        responseMap.put(certList.get(1), DECODER.decode(INT_CA_OCSP_RESP));
+        pkrev.setOcspResponses(responseMap);
+        PKIXParameters params =
+                new PKIXParameters(Collections.singleton(anchor));
+        params.addCertPathChecker(pkrev);
+        params.setDate(EVAL_DATE);
+
+        validator.validate(path, params);
+    }
+
+    private static X509Certificate generateCertificate(CertificateFactory cf,
+            String encoded) throws CertificateException {
+        ByteArrayInputStream is = new ByteArrayInputStream(encoded.getBytes());
+        return (X509Certificate)cf.generateCertificate(is);
+    }
+}