|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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. |
|
} |
|
} |
|
} |
|
} |