|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  |  | 
|  | package sun.security.validator; | 
|  |  | 
|  | import java.util.*; | 
|  |  | 
|  | import java.security.*; | 
|  | import java.security.cert.*; | 
|  |  | 
|  | import javax.security.auth.x500.X500Principal; | 
|  | import sun.security.action.GetBooleanAction; | 
|  | import sun.security.provider.certpath.AlgorithmChecker; | 
|  | import sun.security.provider.certpath.PKIXExtendedParameters; | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  | public final class PKIXValidator extends Validator { | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private static final boolean checkTLSRevocation = | 
|  |         AccessController.doPrivileged | 
|  |             (new GetBooleanAction("com.sun.net.ssl.checkRevocation")); | 
|  |  | 
|  |     private final Set<X509Certificate> trustedCerts; | 
|  |     private final PKIXBuilderParameters parameterTemplate; | 
|  |     private int certPathLength = -1; | 
|  |  | 
|  |      | 
|  |     private final Map<X500Principal, List<PublicKey>> trustedSubjects; | 
|  |     private final CertificateFactory factory; | 
|  |  | 
|  |     private final boolean plugin; | 
|  |  | 
|  |     PKIXValidator(String variant, Collection<X509Certificate> trustedCerts) { | 
|  |         super(TYPE_PKIX, variant); | 
|  |         this.trustedCerts = (trustedCerts instanceof Set) ? | 
|  |                             (Set<X509Certificate>)trustedCerts : | 
|  |                             new HashSet<X509Certificate>(trustedCerts); | 
|  |  | 
|  |         Set<TrustAnchor> trustAnchors = new HashSet<>(); | 
|  |         for (X509Certificate cert : trustedCerts) { | 
|  |             trustAnchors.add(new TrustAnchor(cert, null)); | 
|  |         } | 
|  |  | 
|  |         try { | 
|  |             parameterTemplate = new PKIXBuilderParameters(trustAnchors, null); | 
|  |             factory = CertificateFactory.getInstance("X.509"); | 
|  |         } catch (InvalidAlgorithmParameterException e) { | 
|  |             throw new RuntimeException("Unexpected error: " + e.toString(), e); | 
|  |         } catch (CertificateException e) { | 
|  |             throw new RuntimeException("Internal error", e); | 
|  |         } | 
|  |  | 
|  |         setDefaultParameters(variant); | 
|  |         plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING); | 
|  |  | 
|  |         trustedSubjects = setTrustedSubjects(); | 
|  |     } | 
|  |  | 
|  |     PKIXValidator(String variant, PKIXBuilderParameters params) { | 
|  |         super(TYPE_PKIX, variant); | 
|  |         trustedCerts = new HashSet<X509Certificate>(); | 
|  |         for (TrustAnchor anchor : params.getTrustAnchors()) { | 
|  |             X509Certificate cert = anchor.getTrustedCert(); | 
|  |             if (cert != null) { | 
|  |                 trustedCerts.add(cert); | 
|  |             } | 
|  |         } | 
|  |         parameterTemplate = params; | 
|  |  | 
|  |         try { | 
|  |             factory = CertificateFactory.getInstance("X.509"); | 
|  |         } catch (CertificateException e) { | 
|  |             throw new RuntimeException("Internal error", e); | 
|  |         } | 
|  |  | 
|  |         plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING); | 
|  |  | 
|  |         trustedSubjects = setTrustedSubjects(); | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private Map<X500Principal, List<PublicKey>> setTrustedSubjects() { | 
|  |         Map<X500Principal, List<PublicKey>> subjectMap = new HashMap<>(); | 
|  |  | 
|  |         for (X509Certificate cert : trustedCerts) { | 
|  |             X500Principal dn = cert.getSubjectX500Principal(); | 
|  |             List<PublicKey> keys; | 
|  |             if (subjectMap.containsKey(dn)) { | 
|  |                 keys = subjectMap.get(dn); | 
|  |             } else { | 
|  |                 keys = new ArrayList<PublicKey>(); | 
|  |                 subjectMap.put(dn, keys); | 
|  |             } | 
|  |             keys.add(cert.getPublicKey()); | 
|  |         } | 
|  |  | 
|  |         return subjectMap; | 
|  |     } | 
|  |  | 
|  |     public Collection<X509Certificate> getTrustedCertificates() { | 
|  |         return trustedCerts; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     public int getCertPathLength() {  | 
|  |         return certPathLength; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private void setDefaultParameters(String variant) { | 
|  |         if ((variant == Validator.VAR_TLS_SERVER) || | 
|  |                 (variant == Validator.VAR_TLS_CLIENT)) { | 
|  |             parameterTemplate.setRevocationEnabled(checkTLSRevocation); | 
|  |         } else { | 
|  |             parameterTemplate.setRevocationEnabled(false); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     public PKIXBuilderParameters getParameters() {  | 
|  |         return parameterTemplate; | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     X509Certificate[] engineValidate(X509Certificate[] chain, | 
|  |             Collection<X509Certificate> otherCerts, | 
|  |             List<byte[]> responseList, | 
|  |             AlgorithmConstraints constraints, | 
|  |             Object parameter) throws CertificateException { | 
|  |         if ((chain == null) || (chain.length == 0)) { | 
|  |             throw new CertificateException | 
|  |                 ("null or zero-length certificate chain"); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         PKIXBuilderParameters pkixParameters = null; | 
|  |         try { | 
|  |             pkixParameters = new PKIXExtendedParameters( | 
|  |                     (PKIXBuilderParameters) parameterTemplate.clone(), | 
|  |                     (parameter instanceof Timestamp) ? | 
|  |                             (Timestamp) parameter : null, | 
|  |                     variant); | 
|  |         } catch (InvalidAlgorithmParameterException e) { | 
|  |             // ignore exception | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (constraints != null) { | 
|  |             pkixParameters.addCertPathChecker( | 
|  |                     new AlgorithmChecker(constraints, null, variant)); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (!responseList.isEmpty()) { | 
|  |             addResponses(pkixParameters, chain, responseList); | 
|  |         } | 
|  |  | 
|  |         // check that chain is in correct order and check if chain contains | 
|  |          | 
|  |         X500Principal prevIssuer = null; | 
|  |         for (int i = 0; i < chain.length; i++) { | 
|  |             X509Certificate cert = chain[i]; | 
|  |             X500Principal dn = cert.getSubjectX500Principal(); | 
|  |             if (i != 0 && !dn.equals(prevIssuer)) { | 
|  |                  | 
|  |                 return doBuild(chain, otherCerts, pkixParameters); | 
|  |             } | 
|  |  | 
|  |             // Check if chain[i] is already trusted. It may be inside | 
|  |             // trustedCerts, or has the same dn and public key as a cert | 
|  |             // inside trustedCerts. The latter happens when a CA has | 
|  |             // updated its cert with a stronger signature algorithm in JRE | 
|  |             // but the weak one is still in circulation. | 
|  |  | 
|  |             if (trustedCerts.contains(cert) ||           | 
|  |                     (trustedSubjects.containsKey(dn) &&  | 
|  |                      trustedSubjects.get(dn).contains(   | 
|  |                         cert.getPublicKey()))) { | 
|  |                 if (i == 0) { | 
|  |                     return new X509Certificate[] {chain[0]}; | 
|  |                 } | 
|  |                  | 
|  |                 X509Certificate[] newChain = new X509Certificate[i]; | 
|  |                 System.arraycopy(chain, 0, newChain, 0, i); | 
|  |                 return doValidate(newChain, pkixParameters); | 
|  |             } | 
|  |             prevIssuer = cert.getIssuerX500Principal(); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         X509Certificate last = chain[chain.length - 1]; | 
|  |         X500Principal issuer = last.getIssuerX500Principal(); | 
|  |         X500Principal subject = last.getSubjectX500Principal(); | 
|  |         if (trustedSubjects.containsKey(issuer) && | 
|  |                 isSignatureValid(trustedSubjects.get(issuer), last)) { | 
|  |             return doValidate(chain, pkixParameters); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (plugin) { | 
|  |             // Validate chain even if no trust anchor is found. This | 
|  |             // allows plugin/webstart to make sure the chain is | 
|  |              | 
|  |             if (chain.length > 1) { | 
|  |                 X509Certificate[] newChain = | 
|  |                     new X509Certificate[chain.length-1]; | 
|  |                 System.arraycopy(chain, 0, newChain, 0, newChain.length); | 
|  |  | 
|  |                  | 
|  |                 try { | 
|  |                     pkixParameters.setTrustAnchors | 
|  |                         (Collections.singleton(new TrustAnchor | 
|  |                             (chain[chain.length-1], null))); | 
|  |                 } catch (InvalidAlgorithmParameterException iape) { | 
|  |                      | 
|  |                     throw new CertificateException(iape); | 
|  |                 } | 
|  |                 doValidate(newChain, pkixParameters); | 
|  |             } | 
|  |             // if the rest of the chain is valid, throw exception | 
|  |              | 
|  |             throw new ValidatorException | 
|  |                 (ValidatorException.T_NO_TRUST_ANCHOR); | 
|  |         } | 
|  |         // otherwise, fall back to builder | 
|  |  | 
|  |         return doBuild(chain, otherCerts, pkixParameters); | 
|  |     } | 
|  |  | 
|  |     private boolean isSignatureValid(List<PublicKey> keys, | 
|  |             X509Certificate sub) { | 
|  |         if (plugin) { | 
|  |             for (PublicKey key: keys) { | 
|  |                 try { | 
|  |                     sub.verify(key); | 
|  |                     return true; | 
|  |                 } catch (Exception ex) { | 
|  |                     continue; | 
|  |                 } | 
|  |             } | 
|  |             return false; | 
|  |         } | 
|  |         return true;  | 
|  |     } | 
|  |  | 
|  |     private static X509Certificate[] toArray(CertPath path, TrustAnchor anchor) | 
|  |             throws CertificateException { | 
|  |         List<? extends java.security.cert.Certificate> list = | 
|  |                                                 path.getCertificates(); | 
|  |         X509Certificate[] chain = new X509Certificate[list.size() + 1]; | 
|  |         list.toArray(chain); | 
|  |         X509Certificate trustedCert = anchor.getTrustedCert(); | 
|  |         if (trustedCert == null) { | 
|  |             throw new ValidatorException | 
|  |                 ("TrustAnchor must be specified as certificate"); | 
|  |         } | 
|  |         chain[chain.length - 1] = trustedCert; | 
|  |         return chain; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private void setDate(PKIXBuilderParameters params) { | 
|  |         @SuppressWarnings("deprecation") | 
|  |         Date date = validationDate; | 
|  |         if (date != null) { | 
|  |             params.setDate(date); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private X509Certificate[] doValidate(X509Certificate[] chain, | 
|  |             PKIXBuilderParameters params) throws CertificateException { | 
|  |         try { | 
|  |             setDate(params); | 
|  |  | 
|  |              | 
|  |             CertPathValidator validator = CertPathValidator.getInstance("PKIX"); | 
|  |             CertPath path = factory.generateCertPath(Arrays.asList(chain)); | 
|  |             certPathLength = chain.length; | 
|  |             PKIXCertPathValidatorResult result = | 
|  |                 (PKIXCertPathValidatorResult)validator.validate(path, params); | 
|  |  | 
|  |             return toArray(path, result.getTrustAnchor()); | 
|  |         } catch (GeneralSecurityException e) { | 
|  |             throw new ValidatorException | 
|  |                 ("PKIX path validation failed: " + e.toString(), e); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private X509Certificate[] doBuild(X509Certificate[] chain, | 
|  |         Collection<X509Certificate> otherCerts, | 
|  |         PKIXBuilderParameters params) throws CertificateException { | 
|  |  | 
|  |         try { | 
|  |             setDate(params); | 
|  |  | 
|  |              | 
|  |             X509CertSelector selector = new X509CertSelector(); | 
|  |             selector.setCertificate(chain[0]); | 
|  |             params.setTargetCertConstraints(selector); | 
|  |  | 
|  |              | 
|  |             Collection<X509Certificate> certs = | 
|  |                                         new ArrayList<X509Certificate>(); | 
|  |             certs.addAll(Arrays.asList(chain)); | 
|  |             if (otherCerts != null) { | 
|  |                 certs.addAll(otherCerts); | 
|  |             } | 
|  |             CertStore store = CertStore.getInstance("Collection", | 
|  |                                 new CollectionCertStoreParameters(certs)); | 
|  |             params.addCertStore(store); | 
|  |  | 
|  |              | 
|  |             CertPathBuilder builder = CertPathBuilder.getInstance("PKIX"); | 
|  |             PKIXCertPathBuilderResult result = | 
|  |                 (PKIXCertPathBuilderResult)builder.build(params); | 
|  |  | 
|  |             return toArray(result.getCertPath(), result.getTrustAnchor()); | 
|  |         } catch (GeneralSecurityException e) { | 
|  |             throw new ValidatorException | 
|  |                 ("PKIX path building failed: " + e.toString(), e); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private static void addResponses(PKIXBuilderParameters pkixParams, | 
|  |             X509Certificate[] chain, List<byte[]> responseList) { | 
|  |  | 
|  |         if (pkixParams.isRevocationEnabled()) { | 
|  |             try { | 
|  |                  | 
|  |                 PKIXRevocationChecker revChecker = null; | 
|  |                 List<PKIXCertPathChecker> checkerList = | 
|  |                         new ArrayList<>(pkixParams.getCertPathCheckers()); | 
|  |  | 
|  |                  | 
|  |                 for (PKIXCertPathChecker checker : checkerList) { | 
|  |                     if (checker instanceof PKIXRevocationChecker) { | 
|  |                         revChecker = (PKIXRevocationChecker)checker; | 
|  |                         break; | 
|  |                     } | 
|  |                 } | 
|  |  | 
|  |                  | 
|  |                 if (revChecker == null) { | 
|  |                     revChecker = (PKIXRevocationChecker)CertPathValidator. | 
|  |                             getInstance("PKIX").getRevocationChecker(); | 
|  |                     checkerList.add(revChecker); | 
|  |                 } | 
|  |  | 
|  |                 // Each response in the list should be in parallel with | 
|  |                 // the certificate list.  If there is a zero-length response | 
|  |                 // treat it as being absent.  If the user has provided their | 
|  |                 // own PKIXRevocationChecker with pre-populated responses, do | 
|  |                  | 
|  |                 Map<X509Certificate, byte[]> responseMap = | 
|  |                         revChecker.getOcspResponses(); | 
|  |                 int limit = Integer.min(chain.length, responseList.size()); | 
|  |                 for (int idx = 0; idx < limit; idx++) { | 
|  |                     byte[] respBytes = responseList.get(idx); | 
|  |                     if (respBytes != null && respBytes.length > 0 && | 
|  |                             !responseMap.containsKey(chain[idx])) { | 
|  |                         responseMap.put(chain[idx], respBytes); | 
|  |                     } | 
|  |                 } | 
|  |  | 
|  |                 // Add the responses and push it all back into the | 
|  |                  | 
|  |                 revChecker.setOcspResponses(responseMap); | 
|  |                 pkixParams.setCertPathCheckers(checkerList); | 
|  |             } catch (NoSuchAlgorithmException exc) { | 
|  |                 // This should not occur, but if it does happen then | 
|  |                 // stapled OCSP responses won't be part of revocation checking. | 
|  |                 // Clients can still fall back to other means of revocation | 
|  |                 // checking. | 
|  |             } | 
|  |         } | 
|  |     } | 
|  | } |