Android系统在建立HTTPS三次握手后会进行证书校验,接下来根据android 4.4系统代码来看一下证书校验流程
从org.conscrypt.TrustManagerImpl开始介绍
1、证书校验入口函数
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
checkTrusted(chain, authType, null, false);
}
2、下一个调用函数
private List checkTrusted(X509Certificate[] chain, String authType,
String host, boolean clientAuth)
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);
}
// get the cleaned up chain and trust anchor
Set trustAnchor = new HashSet(); // there can only be one!
X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor);
// add the first trust anchor to the chain, which may be an intermediate
List wholeChain = new ArrayList();
wholeChain.addAll(Arrays.asList(newChain));
// trustAnchor is actually just a single element
for (TrustAnchor trust : trustAnchor) {
wholeChain.add(trust.getTrustedCert());
}
// add all the cached certificates from the cert index, avoiding loops
// this gives us a full chain from leaf to root, which we use for cert pinning and pass
// back out to callers when we return.
X509Certificate last = wholeChain.get(wholeChain.size() - 1);
while (true) {
TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last);
// the cachedTrust can be null if there isn't anything in the index or if a user has
// trusted a non-self-signed cert.
if (cachedTrust == null) {
break;
}
// at this point we have a cached trust anchor, but don't know if its one we got from
// the server. Extract the cert, compare it to the last element in the chain, and add it
// if we haven't seen it before.
X509Certificate next = cachedTrust.getTrustedCert();
if (next != last) {
wholeChain.add(next);
last = next;
} else {
// if next == last then we found a self-signed cert and the chain is done
break;
}
}
// build the cert path from the array of certs sans trust anchors
CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
if (host != null) {
boolean chainIsNotPinned = true;
try {
chainIsNotPinned = pinManager.chainIsNotPinned(host, wholeChain);
} catch (PinManagerException e) {
throw new CertificateException(e);
}
if (chainIsNotPinned) {
throw new CertificateException(new CertPathValidatorException(
"Certificate path is not properly pinned.", null, certPath, -1));
}
}
if (newChain.length == 0) {
// chain was entirely trusted, skip the validator
return wholeChain;
}
if (trustAnchor.isEmpty()) {
throw new CertificateException(new CertPathValidatorException(
"Trust anchor for certification path not found.", null, certPath, -1));
}
// There's no point in checking trust anchors here, and it will throw off the MD5 check,
// so we just hand it the chain without anchors
ChainStrengthAnalyzer.check(newChain);
try {
PKIXParameters params = new PKIXParameters(trustAnchor);
params.setRevocationEnabled(false);
params.addCertPathChecker(new ExtendedKeyUsagePKIXCertPathChecker(clientAuth,
newChain[0]));
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);
}
return wholeChain;
}
这个函数里面主要内容是:
1、整理证书链,调用函数是cleanupCertChainAndFindTrustAnchors:
private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain,
Set 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++) {
// Walk the chain to find a "subject" matching
// the "issuer" of the current certificate. In a properly
// ordered 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. Find the trust anchor in the chain, if any
int anchorIndex;
for (anchorIndex = 0; anchorIndex <= currIndex; anchorIndex++) {
// 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 compatibility.
TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]);
if (trustAnchor != null) {
trustAnchors.add(trustAnchor);
break;
}
}
// 3. If the chain is now shorter, copy to an appropriately sized array.
int chainLength = anchorIndex;
X509Certificate[] newChain = ((chainLength == chain.length)
? chain
: Arrays.copyOf(chain, chainLength));
// 4. If we didn't find a trust anchor earlier, look for one now
if (trustAnchors.isEmpty()) {
TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]);
if (trustAnchor != null) {
trustAnchors.add(trustAnchor);
}
}
return newChain;
}
该函数主要功能是将传进来的证书链的证书按subject和issuer确立父子关系,调整好顺序,删除不相关的证书,然后从证书链第一个证书开始寻找系统中与它subject和public key相同的证书作为信任证书,如果找到的话就会返回新的证书链,这个证书链根证书就是第一个找到系统对应证书的那个证书
备注:如果证书链中只有一个证书,那么只会校验该证书的subject和public key是否和系统中的某一个证书一样,并不会像校验证书链那样用public key去校验证书签名,之所以这样,我的理解是因为https是公钥加密私钥解密的非对称加密,所以私钥不一样公钥肯定也不一样,所以即使你把公钥改成和系统证书一样,那你拿到数据后你没有对应的私钥也解不出来,所以只要校验公钥是否一样就足够了。
2、整理好证书链以及获取到系统的一个证书后开始进行校验,校验函数是validator.validate(certPath, params);先来看一下validator这个变量哪来的
public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) {
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;
}
if (manager != null) {
this.pinManager = manager;
} else {
try {
pinManager = new CertPinManager(trustedCertificateStoreLocal);
} catch (PinManagerException e) {
throw new SecurityException("Could not initialize CertPinManager", e);
}
}
this.rootKeyStore = rootKeyStoreLocal;
this.trustedCertificateStore = trustedCertificateStoreLocal;
this.validator = validatorLocal;
this.factory = factoryLocal;
this.trustedCertificateIndex = trustedCertificateIndexLocal;
this.acceptedIssuers = acceptedIssuersLocal;
this.err = errLocal;
}
可以看到这个变量是通过validatorLocal = CertPathValidator.getInstance("PKIX");创建的,返回的实例是security provider中的一个对象,具体路径是
org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi,校验是调用validate函数,这个函数定义在类java.security.cert.CertPathValidator里:
public final CertPathValidatorResult validate(CertPath certPath,
CertPathParameters params) throws CertPathValidatorException,
InvalidAlgorithmParameterException {
return spiImpl.engineValidate(certPath, params);
}
可以看到它调用了它的实现类里面的engineValidate
方法,这个实现类就是上面说的
PKIXCertPathBuilderSpi,接着我们来看一下这个函数具体内容:
public CertPathValidatorResult engineValidate(
CertPath certPath,
CertPathParameters params)
throws CertPathValidatorException,
InvalidAlgorithmParameterException
{
if (!(params instanceof PKIXParameters))
{
throw new InvalidAlgorithmParameterException("Parameters must be a " + PKIXParameters.class.getName()
+ " instance.");
}
ExtendedPKIXParameters paramsPKIX;
if (params instanceof ExtendedPKIXParameters)
{
paramsPKIX = (ExtendedPKIXParameters)params;
}
else
{
paramsPKIX = ExtendedPKIXParameters.getInstance((PKIXParameters)params);
}
if (paramsPKIX.getTrustAnchors() == null)
{
throw new InvalidAlgorithmParameterException(
"trustAnchors is null, this is not allowed for certification path validation.");
}
//
// 6.1.1 - inputs
//
//
// (a)
//
List certs = certPath.getCertificates();
int n = certs.size();
if (certs.isEmpty())
{
throw new CertPathValidatorException("Certification path is empty.", null, certPath, 0);
}
// BEGIN android-added
{
X509Certificate cert = (X509Certificate) certs.get(0);
if (cert != null) {
BigInteger serial = cert.getSerialNumber();
if (blacklist.isSerialNumberBlackListed(serial)) {
// emulate CRL exception message in RFC3280CertPathUtilities.checkCRLs
String message = "Certificate revocation of serial 0x" + serial.toString(16);
System.out.println(message);
AnnotatedException e = new AnnotatedException(message);
throw new CertPathValidatorException(e.getMessage(), e, certPath, 0);
}
}
}
// END android-added
//
// (b)
//
// Date validDate = CertPathValidatorUtilities.getValidDate(paramsPKIX);
//
// (c)
//
Set userInitialPolicySet = paramsPKIX.getInitialPolicies();
//
// (d)
//
TrustAnchor trust;
try
{
trust = CertPathValidatorUtilities.findTrustAnchor((X509Certificate) certs.get(certs.size() - 1),
paramsPKIX.getTrustAnchors(), paramsPKIX.getSigProvider());
}
catch (AnnotatedException e)
{
throw new CertPathValidatorException(e.getMessage(), e, certPath, certs.size() - 1);
}
if (trust == null)
{
throw new CertPathValidatorException("Trust anchor for certification path not found.", null, certPath, -1);
}
//
// (e), (f), (g) are part of the paramsPKIX object.
//
Iterator certIter;
int index = 0;
int i;
// Certificate for each interation of the validation loop
// Signature information for each iteration of the validation loop
//
// 6.1.2 - setup
//
//
// (a)
//
List[] policyNodes = new ArrayList[n + 1];
for (int j = 0; j < policyNodes.length; j++)
{
policyNodes[j] = new ArrayList();
}
Set policySet = new HashSet();
policySet.add(RFC3280CertPathUtilities.ANY_POLICY);
PKIXPolicyNode validPolicyTree = new PKIXPolicyNode(new ArrayList(), 0, policySet, null, new HashSet(),
RFC3280CertPathUtilities.ANY_POLICY, false);
policyNodes[0].add(validPolicyTree);
//
// (b) and (c)
//
PKIXNameConstraintValidator nameConstraintValidator = new PKIXNameConstraintValidator();
// (d)
//
int explicitPolicy;
Set acceptablePolicies = new HashSet();
if (paramsPKIX.isExplicitPolicyRequired())
{
explicitPolicy = 0;
}
else
{
explicitPolicy = n + 1;
}
//
// (e)
//
int inhibitAnyPolicy;
if (paramsPKIX.isAnyPolicyInhibited())
{
inhibitAnyPolicy = 0;
}
else
{
inhibitAnyPolicy = n + 1;
}
//
// (f)
//
int policyMapping;
if (paramsPKIX.isPolicyMappingInhibited())
{
policyMapping = 0;
}
else
{
policyMapping = n + 1;
}
//
// (g), (h), (i), (j)
//
PublicKey workingPublicKey;
X500Principal workingIssuerName;
X509Certificate sign = trust.getTrustedCert();
try
{
if (sign != null)
{
workingIssuerName = CertPathValidatorUtilities.getSubjectPrincipal(sign);
workingPublicKey = sign.getPublicKey();
}
else
{
workingIssuerName = new X500Principal(trust.getCAName());
workingPublicKey = trust.getCAPublicKey();
}
}
catch (IllegalArgumentException ex)
{
throw new ExtCertPathValidatorException("Subject of trust anchor could not be (re)encoded.", ex, certPath,
-1);
}
AlgorithmIdentifier workingAlgId = null;
try
{
workingAlgId = CertPathValidatorUtilities.getAlgorithmIdentifier(workingPublicKey);
}
catch (CertPathValidatorException e)
{
throw new ExtCertPathValidatorException(
"Algorithm identifier of public key of trust anchor could not be read.", e, certPath, -1);
}
DERObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.getObjectId();
ASN1Encodable workingPublicKeyParameters = workingAlgId.getParameters();
//
// (k)
//
int maxPathLength = n;
//
// 6.1.3
//
if (paramsPKIX.getTargetConstraints() != null
&& !paramsPKIX.getTargetConstraints().match((X509Certificate) certs.get(0)))
{
throw new ExtCertPathValidatorException(
"Target certificate in certification path does not match targetConstraints.", null, certPath, 0);
}
//
// initialize CertPathChecker's
//
List pathCheckers = paramsPKIX.getCertPathCheckers();
certIter = pathCheckers.iterator();
while (certIter.hasNext())
{
((PKIXCertPathChecker) certIter.next()).init(false);
}
X509Certificate cert = null;
for (index = certs.size() - 1; index >= 0; index--)
{
// BEGIN android-added
if (blacklist.isPublicKeyBlackListed(workingPublicKey)) {
// emulate CRL exception message in RFC3280CertPathUtilities.checkCRLs
String message = "Certificate revocation of public key " + workingPublicKey;
System.out.println(message);
AnnotatedException e = new AnnotatedException(message);
throw new CertPathValidatorException(e.getMessage(), e, certPath, index);
}
// END android-added
// try
// {
//
// i as defined in the algorithm description
//
i = n - index;
//
// set certificate to be checked in this round
// sign and workingPublicKey and workingIssuerName are set
// at the end of the for loop and initialized the
// first time from the TrustAnchor
//
cert = (X509Certificate) certs.get(index);
boolean verificationAlreadyPerformed = (index == certs.size() - 1);
//
// 6.1.3
//
RFC3280CertPathUtilities.processCertA(certPath, paramsPKIX, index, workingPublicKey,
verificationAlreadyPerformed, workingIssuerName, sign);
RFC3280CertPathUtilities.processCertBC(certPath, index, nameConstraintValidator);
validPolicyTree = RFC3280CertPathUtilities.processCertD(certPath, index, acceptablePolicies,
validPolicyTree, policyNodes, inhibitAnyPolicy);
validPolicyTree = RFC3280CertPathUtilities.processCertE(certPath, index, validPolicyTree);
RFC3280CertPathUtilities.processCertF(certPath, index, validPolicyTree, explicitPolicy);
//
// 6.1.4
//
if (i != n)
{
if (cert != null && cert.getVersion() == 1)
{
throw new CertPathValidatorException("Version 1 certificates can't be used as CA ones.", null,
certPath, index);
}
RFC3280CertPathUtilities.prepareNextCertA(certPath, index);
validPolicyTree = RFC3280CertPathUtilities.prepareCertB(certPath, index, policyNodes, validPolicyTree,
policyMapping);
RFC3280CertPathUtilities.prepareNextCertG(certPath, index, nameConstraintValidator);
// (h)
explicitPolicy = RFC3280CertPathUtilities.prepareNextCertH1(certPath, index, explicitPolicy);
policyMapping = RFC3280CertPathUtilities.prepareNextCertH2(certPath, index, policyMapping);
inhibitAnyPolicy = RFC3280CertPathUtilities.prepareNextCertH3(certPath, index, inhibitAnyPolicy);
//
// (i)
//
explicitPolicy = RFC3280CertPathUtilities.prepareNextCertI1(certPath, index, explicitPolicy);
policyMapping = RFC3280CertPathUtilities.prepareNextCertI2(certPath, index, policyMapping);
// (j)
inhibitAnyPolicy = RFC3280CertPathUtilities.prepareNextCertJ(certPath, index, inhibitAnyPolicy);
// (k)
RFC3280CertPathUtilities.prepareNextCertK(certPath, index);
// (l)
maxPathLength = RFC3280CertPathUtilities.prepareNextCertL(certPath, index, maxPathLength);
// (m)
maxPathLength = RFC3280CertPathUtilities.prepareNextCertM(certPath, index, maxPathLength);
// (n)
RFC3280CertPathUtilities.prepareNextCertN(certPath, index);
Set criticalExtensions = cert.getCriticalExtensionOIDs();
if (criticalExtensions != null)
{
criticalExtensions = new HashSet(criticalExtensions);
// these extensions are handled by the algorithm
criticalExtensions.remove(RFC3280CertPathUtilities.KEY_USAGE);
criticalExtensions.remove(RFC3280CertPathUtilities.CERTIFICATE_POLICIES);
criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_MAPPINGS);
criticalExtensions.remove(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY);
criticalExtensions.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
criticalExtensions.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_CONSTRAINTS);
criticalExtensions.remove(RFC3280CertPathUtilities.BASIC_CONSTRAINTS);
criticalExtensions.remove(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME);
criticalExtensions.remove(RFC3280CertPathUtilities.NAME_CONSTRAINTS);
}
else
{
criticalExtensions = new HashSet();
}
// (o)
RFC3280CertPathUtilities.prepareNextCertO(certPath, index, criticalExtensions, pathCheckers);
// set signing certificate for next round
sign = cert;
// (c)
workingIssuerName = CertPathValidatorUtilities.getSubjectPrincipal(sign);
// (d)
try
{
workingPublicKey = CertPathValidatorUtilities.getNextWorkingKey(certPath.getCertificates(), index);
}
catch (CertPathValidatorException e)
{
throw new CertPathValidatorException("Next working key could not be retrieved.", e, certPath, index);
}
workingAlgId = CertPathValidatorUtilities.getAlgorithmIdentifier(workingPublicKey);
// (f)
workingPublicKeyAlgorithm = workingAlgId.getObjectId();
// (e)
workingPublicKeyParameters = workingAlgId.getParameters();
}
}
...
这个函数内容比较多,主要截取前面部分,大概流程是
1、找到我们之前整理证书链时找到的那个对应的系统证书
2、用找到的系统证书的public key校验证书链根证书,方法如下:
protected static void verifyX509Certificate(X509Certificate cert, PublicKey publicKey,
String sigProvider)
throws GeneralSecurityException
{
if (sigProvider == null)
{
cert.verify(publicKey);
}
else
{
cert.verify(publicKey, sigProvider);
}
}
3、开始校验证书链,其中校验签名的函数是processCertA:
protected static void processCertA(
CertPath certPath,
ExtendedPKIXParameters paramsPKIX,
int index,
PublicKey workingPublicKey,
boolean verificationAlreadyPerformed,
X500Principal workingIssuerName,
X509Certificate sign)
throws ExtCertPathValidatorException
{
List certs = certPath.getCertificates();
X509Certificate cert = (X509Certificate)certs.get(index);
//
// (a) verify
//
if (!verificationAlreadyPerformed)
{
try
{
// (a) (1)
//
CertPathValidatorUtilities.verifyX509Certificate(cert, workingPublicKey,
paramsPKIX.getSigProvider());
}
catch (GeneralSecurityException e)
{
throw new ExtCertPathValidatorException("Could not validate certificate signature.", e, certPath, index);
}
}
try
{
// (a) (2)
//
cert.checkValidity(CertPathValidatorUtilities
.getValidCertDateFromValidityModel(paramsPKIX, certPath, index));
}
catch (CertificateExpiredException e)
{
throw new ExtCertPathValidatorException("Could not validate certificate: " + e.getMessage(), e, certPath, index);
}
catch (CertificateNotYetValidException e)
{
throw new ExtCertPathValidatorException("Could not validate certificate: " + e.getMessage(), e, certPath, index);
}
catch (AnnotatedException e)
{
throw new ExtCertPathValidatorException("Could not validate time of certificate.", e, certPath, index);
}
//
// (a) (3)
//
if (paramsPKIX.isRevocationEnabled())
{
try
{
checkCRLs(paramsPKIX, cert, CertPathValidatorUtilities.getValidCertDateFromValidityModel(paramsPKIX,
certPath, index), sign, workingPublicKey, certs);
}
catch (AnnotatedException e)
{
Throwable cause = e;
if (null != e.getCause())
{
cause = e.getCause();
}
throw new ExtCertPathValidatorException(e.getMessage(), cause, certPath, index);
}
}
//
// (a) (4) name chaining
//
if (!CertPathValidatorUtilities.getEncodedIssuerPrincipal(cert).equals(workingIssuerName))
{
throw new ExtCertPathValidatorException("IssuerName(" + CertPathValidatorUtilities.getEncodedIssuerPrincipal(cert)
+ ") does not match SubjectName(" + workingIssuerName + ") of signing certificate.", null,
certPath, index);
}
}
这个函数同样是用public key校验签名以及校验证书的有效性
4、按照3中的方法从证书链的根证书的下一个证书开始每次拿上一个证书的public key校验下一个证书,直到证书链最底端的证书,任何一步校验错误都会认为证书不合法
5、除了校验签名以外,还有很多校验,具体可以查看
org.bouncycastle.jce.provider.RFC3280CertPathUtilities这个类