blob: 653dca4ede7413ba84b914e19bb8d359aa05e090 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.harmony.xnet.provider.jsse;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.X509TrustManager;
/**
*
* TrustManager implementation. The implementation is based on CertPathValidator
* PKIX and CertificateFactory X509 implementations. This implementations should
* be provided by some certification provider.
*
* @see javax.net.ssl.X509TrustManager
*/
public final class TrustManagerImpl implements X509TrustManager {
/**
* The AndroidCAStore if non-null, null otherwise.
*/
private final KeyStore rootKeyStore;
/**
* The backing store for the AndroidCAStore if non-null. This will
* be null when the rootKeyStore is null, implying we are not
* using the AndroidCAStore.
*/
private final TrustedCertificateStore trustedCertificateStore;
private final CertPathValidator validator;
/**
* An index of TrustAnchor instances that we've seen. Unlike the
* TrustedCertificateStore, this may contain intermediate CAs.
*/
private final TrustedCertificateIndex trustedCertificateIndex;
/**
* This is lazily initialized in the AndroidCAStore case since it
* forces us to bring all the CAs into memory. In the
* non-AndroidCAStore, we initialize this as part of the
* constructor.
*/
private final X509Certificate[] acceptedIssuers;
private final Exception err;
private final CertificateFactory factory;
/**
* Creates X509TrustManager based on a keystore
*
* @param ks
*/
public TrustManagerImpl(KeyStore keyStore) {
CertPathValidator validatorLocal = null;
CertificateFactory factoryLocal = null;
KeyStore rootKeyStoreLocal = null;
TrustedCertificateStore trustedCertificateStoreLocal = null;
TrustedCertificateIndex trustedCertificateIndexLocal = null;
X509Certificate[] acceptedIssuersLocal = null;
Exception errLocal = null;
try {
validatorLocal = CertPathValidator.getInstance("PKIX");
factoryLocal = CertificateFactory.getInstance("X509");
// if we have an AndroidCAStore, we will lazily load CAs
if ("AndroidCAStore".equals(keyStore.getType())) {
rootKeyStoreLocal = keyStore;
trustedCertificateStoreLocal = new TrustedCertificateStore();
acceptedIssuersLocal = null;
trustedCertificateIndexLocal = new TrustedCertificateIndex();
} else {
rootKeyStoreLocal = null;
trustedCertificateStoreLocal = null;
acceptedIssuersLocal = acceptedIssuers(keyStore);
trustedCertificateIndexLocal
= new TrustedCertificateIndex(trustAnchors(acceptedIssuersLocal));
}
} catch (Exception e) {
errLocal = e;
}
this.rootKeyStore = rootKeyStoreLocal;
this.trustedCertificateStore = trustedCertificateStoreLocal;
this.validator = validatorLocal;
this.factory = factoryLocal;
this.trustedCertificateIndex = trustedCertificateIndexLocal;
this.acceptedIssuers = acceptedIssuersLocal;
this.err = errLocal;
}
private static X509Certificate[] acceptedIssuers(KeyStore ks) {
try {
// Note that unlike the PKIXParameters code to create a Set of
// TrustAnchors from a KeyStore, this version takes from both
// TrustedCertificateEntry and PrivateKeyEntry, not just
// TrustedCertificateEntry, which is why TrustManagerImpl
// cannot just use an PKIXParameters(KeyStore)
// constructor.
// TODO remove duplicates if same cert is found in both a
// PrivateKeyEntry and TrustedCertificateEntry
List<X509Certificate> trusted = new ArrayList<X509Certificate>();
for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) {
final String alias = en.nextElement();
final X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
if (cert != null) {
trusted.add(cert);
}
}
return trusted.toArray(new X509Certificate[trusted.size()]);
} catch (KeyStoreException e) {
return new X509Certificate[0];
}
}
private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) {
Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length);
for (X509Certificate cert : certs) {
trustAnchors.add(new TrustAnchor(cert, null));
}
return trustAnchors;
}
@Override public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
checkTrusted(chain, authType);
}
@Override public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
checkTrusted(chain, authType);
}
private void checkTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) {
throw new IllegalArgumentException("null or zero-length parameter");
}
if (err != null) {
throw new CertificateException(err);
}
Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchors);
if (newChain.length == 0) {
// chain was entirely trusted, skip the validator
return;
}
CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
if (trustAnchors.isEmpty()) {
throw new CertificateException(new CertPathValidatorException(
"Trust anchor for certification path not found.", null, certPath, -1));
}
try {
PKIXParameters params = new PKIXParameters(trustAnchors);
params.setRevocationEnabled(false);
validator.validate(certPath, params);
// Add intermediate CAs to the index to tolerate sites
// that assume that the browser will have cached these.
// The server certificate is skipped by skipping the
// zeroth element of new chain and note that the root CA
// will have been removed in
// cleanupCertChainAndFindTrustAnchors. http://b/3404902
for (int i = 1; i < newChain.length; i++) {
trustedCertificateIndex.index(newChain[i]);
}
} catch (InvalidAlgorithmParameterException e) {
throw new CertificateException(e);
} catch (CertPathValidatorException e) {
throw new CertificateException(e);
}
}
/**
* Clean up the certificate chain, returning a cleaned up chain,
* which may be a new array instance if elements were removed.
* Theoretically, we shouldn't have to do this, but various web
* servers in practice are mis-configured to have out-of-order
* certificates, expired self-issued root certificate, or CAs with
* unsupported signature algorithms such as
* md2WithRSAEncryption. This also handles removing old certs
* after bridge CA certs.
*/
private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain,
Set<TrustAnchor> trustAnchors) {
X509Certificate[] original = chain;
// 1. Clean the received certificates chain.
int currIndex;
// Start with the first certificate in the chain, assuming it
// is the leaf certificate (server or client cert).
for (currIndex = 0; currIndex < chain.length; currIndex++) {
// If the current cert is a TrustAnchor, we can ignore the rest of the chain.
// This avoids including "bridge" CA certs that added for legacy compatability.
TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[currIndex]);
if (trustAnchor != null) {
trustAnchors.add(trustAnchor);
currIndex--;
break;
}
// Walk the rest of the chain to find a "subject" matching
// the "issuer" of the current certificate. In a properly
// order chain this should be the next cert and be fast.
// If not, we reorder things to be as the validator will
// expect.
boolean foundNext = false;
for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) {
if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) {
foundNext = true;
// Exchange certificates so that 0 through currIndex + 1 are in proper order
if (nextIndex != currIndex + 1) {
// don't mutuate original chain, which may be directly from an SSLSession
if (chain == original) {
chain = original.clone();
}
X509Certificate tempCertificate = chain[nextIndex];
chain[nextIndex] = chain[currIndex + 1];
chain[currIndex + 1] = tempCertificate;
}
break;
}
}
// If we can't find the next in the chain, just give up
// and use what we found so far. This drops unrelated
// certificates that have nothing to do with the cert
// chain.
if (!foundNext) {
break;
}
}
// 2. If the chain is now shorter, copy to an appropriately sized array.
int chainLength = currIndex + 1;
X509Certificate[] newChain = ((chainLength == chain.length)
? chain
: Arrays.copyOf(chain, chainLength));
// 3. If no TrustAnchor was found in cleanup, look for one now
if (trustAnchors.isEmpty()) {
TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[chainLength-1]);
if (trustAnchor != null) {
trustAnchors.add(trustAnchor);
}
}
return newChain;
}
/**
* Check the trustedCertificateIndex for the cert to see if it is
* already trusted and failing that check the KeyStore if it is
* available.
*/
private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert);
if (trustAnchor != null) {
return trustAnchor;
}
if (trustedCertificateStore == null) {
// not trusted and no TrustedCertificateStore to check
return null;
}
// probe KeyStore for a cert. AndroidCAStore stores its
// contents hashed by cert subject on the filesystem to make
// this faster than scanning all key store entries.
if (trustedCertificateStore.isTrustAnchor(cert)) {
// add new TrustAnchor to params index to avoid
// checking filesystem next time around.
return trustedCertificateIndex.index(cert);
}
return null;
}
private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) {
TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert);
if (trustAnchor != null) {
return trustAnchor;
}
if (trustedCertificateStore == null) {
return null;
}
// we have a KeyStore and the issuer of the last cert in
// the chain seems to be missing from the
// TrustedCertificateIndex, check the KeyStore for a hit
X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert);
if (issuer != null) {
return trustedCertificateIndex.index(issuer);
}
return null;
}
@Override public X509Certificate[] getAcceptedIssuers() {
return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore);
}
}