Android 7.0系统校验证书流程

Android系统在建立HTTPS三次握手后会进行证书校验,接下来根据android 7.0系统代码来看一下证书校验流程

 

当我们校验证书的时候首先需要创建一个android.security.net.config.RootTrustMangaer对象,然后调用它的checkServerTrusted

1、证书校验入口函数  

@Override
    public void checkServerTrusted(X509Certificate[] certs, String authType)
            throws CertificateException {
        if (mConfig.hasPerDomainConfigs()) {
            throw new CertificateException(
                    "Domain specific configurations require that hostname aware"
                    + " checkServerTrusted(X509Certificate[], String, String) is used");
        }
        NetworkSecurityConfig config = mConfig.getConfigForHostname("");
        config.getTrustManager().checkServerTrusted(certs, authType);
    }

2、config.getTrustManager()返回一个X509TrustManagerImpl的对象,然后调用它的checkServerTrusted方法,如下: 

@Override
 public void checkServerTrusted(X509Certificate chain[], String authType)
            throws CertificateException {
        checkTrusted(chain, authType, (Socket)null, false);
}
 
 
private void checkTrusted(X509Certificate[] chain, String authType,
                Socket socket, boolean isClient) throws CertificateException {
        Validator v = checkTrustedInit(chain, authType, isClient);
 
        AlgorithmConstraints constraints = null;
        if ((socket != null) && socket.isConnected() &&
                                        (socket instanceof SSLSocket)) {
 
            SSLSocket sslSocket = (SSLSocket)socket;
            SSLSession session = sslSocket.getHandshakeSession();
            if (session == null) {
                throw new CertificateException("No handshake session");
            }
 
            // check endpoint identity
            String identityAlg = sslSocket.getSSLParameters().
                                        getEndpointIdentificationAlgorithm();
            if (identityAlg != null && identityAlg.length() != 0) {
                String hostname = session.getPeerHost();
                checkIdentity(hostname, chain[0], identityAlg);
            }
 
            // create the algorithm constraints
            ProtocolVersion protocolVersion =
                ProtocolVersion.valueOf(session.getProtocol());
            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
                if (session instanceof ExtendedSSLSession) {
                    ExtendedSSLSession extSession =
                                    (ExtendedSSLSession)session;
                    String[] localSupportedSignAlgs =
                            extSession.getLocalSupportedSignatureAlgorithms();
 
                    constraints = new SSLAlgorithmConstraints(
                                    sslSocket, localSupportedSignAlgs, false);
                } else {
                    constraints =
                            new SSLAlgorithmConstraints(sslSocket, false);
                }
            } else {
                constraints = new SSLAlgorithmConstraints(sslSocket, false);
            }
        }
 
        X509Certificate[] trustedChain = null;
        if (isClient) {
            trustedChain = validate(v, chain, constraints, null);
        } else {
            trustedChain = validate(v, chain, constraints, authType);
        }
        if (debug != null && Debug.isOn("trustmanager")) {
            System.out.println("Found trusted certificate:");
            System.out.println(trustedChain[trustedChain.length - 1]);
        }
}

这个方法主要调用了一个 validate函数,接下来看看这个函数: 

private static X509Certificate[] validate(Validator v,
            X509Certificate[] chain, AlgorithmConstraints constraints,
            String authType) throws CertificateException {
        Object o = JsseJce.beginFipsProvider();
        try {
            return v.validate(chain, null, constraints, authType);
        } finally {
            JsseJce.endFipsProvider(o);
        }
}

可以看到validate函数主要调用了Validator对象的validate函数,这个Validator是在上面通过checkTrustedInit函数创建的,来看一下这个函数:

private Validator checkTrustedInit(X509Certificate[] chain,
                                        String authType, boolean isClient) {
        if (chain == null || chain.length == 0) {
            throw new IllegalArgumentException(
                "null or zero-length certificate chain");
        }
 
        if (authType == null || authType.length() == 0) {
            throw new IllegalArgumentException(
                "null or zero-length authentication type");
        }
 
        Validator v = null;
        if (isClient) {
            v = clientValidator;
            if (v == null) {
                synchronized (this) {
                    v = clientValidator;
                    if (v == null) {
                        v = getValidator(Validator.VAR_TLS_CLIENT);
                        clientValidator = v;
                    }
                }
            }
        } else {
            // assume double checked locking with a volatile flag works
            // (guaranteed under the new Tiger memory model)
            v = serverValidator;
            if (v == null) {
                synchronized (this) {
                    v = serverValidator;
                    if (v == null) {
                        v = getValidator(Validator.VAR_TLS_SERVER);
                        serverValidator = v;
                    }
                }
            }
        }
 
        return v;
}

可以看到又调用了getValidator这个函数,我们直接看最终调用的那个函数:

 public static Validator getInstance(String type, String variant,
            Collection trustedCerts) {
        if (type.equals(TYPE_SIMPLE)) {
            return new SimpleValidator(variant, trustedCerts);
        } else if (type.equals(TYPE_PKIX)) {
            return new PKIXValidator(variant, trustedCerts);
        } else {
            throw new IllegalArgumentException
                ("Unknown validator type: " + type);
        }
}

到这边大概清楚了,我们接下来主要介绍TYPE_PKIX,所以这里就是生成PKIXValidator对象

 

4、获取到Validator对象后就可以开始校验了,上面我们看到了校验主要是调用了v.validate(chain, null, constraints, authType);,那么我们来看一下这个函数: 

public final X509Certificate[] validate(X509Certificate[] chain,
                Collection otherCerts,
                AlgorithmConstraints constraints,
                Object parameter) throws CertificateException {
        chain = engineValidate(chain, otherCerts, constraints, parameter);
 
        // omit EE extension check if EE cert is also trust anchor
        if (chain.length > 1) {
            endEntityChecker.check(chain[0], parameter);
        }
 
        return chain;
}

这个函数是定义在基类里的,可以看到它调用了engineValidate函数,这个是它的子类实现的,所以我们需要看PKIXValidatorengineValidate方法: 

 @Override
 X509Certificate[] engineValidate(X509Certificate[] chain,
            Collection otherCerts,
            AlgorithmConstraints constraints,
            Object parameter) throws CertificateException {
        if ((chain == null) || (chain.length == 0)) {
            throw new CertificateException
                ("null or zero-length certificate chain");
        }
 
        // add  new algorithm constraints checker
        PKIXBuilderParameters pkixParameters =
                    (PKIXBuilderParameters) parameterTemplate.clone();
        AlgorithmChecker algorithmChecker = null;
        if (constraints != null) {
            algorithmChecker = new AlgorithmChecker(constraints);
            pkixParameters.addCertPathChecker(algorithmChecker);
        }
 
        if (TRY_VALIDATOR) {
            // check that chain is in correct order and check if chain contains
            // trust anchor
            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)) {
                    // chain is not ordered correctly, call builder instead
                    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) ||          // trusted cert
                        (trustedSubjects.containsKey(dn) && // replacing ...
                         trustedSubjects.get(dn).contains(  // ... weak cert
                            cert.getPublicKey()))) {
                    if (i == 0) {
                        return new X509Certificate[] {chain[0]};
                    }
                    // Remove and call validator on partial chain [0 .. i-1]
                    X509Certificate[] newChain = new X509Certificate[i];
                    System.arraycopy(chain, 0, newChain, 0, i);
                    return doValidate(newChain, pkixParameters);
                }
                prevIssuer = cert.getIssuerX500Principal();
            }
 
            // apparently issued by trust anchor?
            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);
            }
 
            // don't fallback to builder if called from plugin/webstart
            if (plugin) {
                // Validate chain even if no trust anchor is found. This
                // allows plugin/webstart to make sure the chain is
                // otherwise valid
                if (chain.length > 1) {
                    X509Certificate[] newChain =
                        new X509Certificate[chain.length-1];
                    System.arraycopy(chain, 0, newChain, 0, newChain.length);
 
                    // temporarily set last cert as sole trust anchor
                    try {
                        pkixParameters.setTrustAnchors
                            (Collections.singleton(new TrustAnchor
                                (chain[chain.length-1], null)));
                    } catch (InvalidAlgorithmParameterException iape) {
                        // should never occur, but ...
                        throw new CertificateException(iape);
                    }
                    doValidate(newChain, pkixParameters);
                }
                // if the rest of the chain is valid, throw exception
                // indicating no trust anchor was found
                throw new ValidatorException
                    (ValidatorException.T_NO_TRUST_ANCHOR);
            }
            // otherwise, fall back to builder
        }
 
        return doBuild(chain, otherCerts, pkixParameters);
}

这个函数主要流程是:

1)整理证书链,这个在之前4.4的系统上也有,将证书链整理好顺序,删除不相关证书,当然如果证书链只有一个证书,那直接比较这个证书的subjectpublic key是否和系统中某个证书一样,如果找到系统中有这个证书,那么就当做校验成功,这点也和4.4的系统一样的逻辑

2)如果证书链证书至少两个,那么同样找到证书链中的第一个系统中有对应的证书时就把这个证书当做根证书,生成新的证书链进行校验

3)如果都没找到系统中对应的证书,那么将整个证书链进行校验

 

5、证书链的校验:

根据4中的结果,拿到证书链进行校验,校验函数主要是doValidate,我们来看一下这个函数:

private X509Certificate[] doValidate(X509Certificate[] chain,
            PKIXBuilderParameters params) throws CertificateException {
        try {
            setDate(params);
 
            // do the validation
            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);
        }
}

这边又看到和4.4系统一样的操作,就是创建一个CertPathValidator对象,因为指定了PKIX,所以最终得到的对象是PKIXCertPathValidator,它的validate函数同样调用了engineValidate函数,接下来看一下这个函数:

 @Override
 public CertPathValidatorResult engineValidate(CertPath cp,
                                                  CertPathParameters params)
        throws CertPathValidatorException, InvalidAlgorithmParameterException
    {
        ValidatorParams valParams = PKIX.checkParams(cp, params);
        return validate(valParams);
    }
 
 private static PKIXCertPathValidatorResult validate(ValidatorParams params)
        throws CertPathValidatorException
    {
        if (debug != null)
            debug.println("PKIXCertPathValidator.engineValidate()...");
 
        // Retrieve the first certificate in the certpath
        // (to be used later in pre-screening)
        AdaptableX509CertSelector selector = null;
        List certList = params.certificates();
        if (!certList.isEmpty()) {
            selector = new AdaptableX509CertSelector();
            X509Certificate firstCert = certList.get(0);
            // check trusted certificate's subject
            selector.setSubject(firstCert.getIssuerX500Principal());
            // check the validity period
            selector.setValidityPeriod(firstCert.getNotBefore(),
                                       firstCert.getNotAfter());
            /*
             * Facilitate certification path construction with authority
             * key identifier and subject key identifier.
             */
            try {
                X509CertImpl firstCertImpl = X509CertImpl.toImpl(firstCert);
                selector.parseAuthorityKeyIdentifierExtension(
                            firstCertImpl.getAuthorityKeyIdentifierExtension());
            } catch (CertificateException | IOException e) {
                // ignore
            }
        }
 
        CertPathValidatorException lastException = null;
 
        // We iterate through the set of trust anchors until we find
        // one that works at which time we stop iterating
        for (TrustAnchor anchor : params.trustAnchors()) {
            X509Certificate trustedCert = anchor.getTrustedCert();
            if (trustedCert != null) {
                // if this trust anchor is not worth trying,
                // we move on to the next one
                if (selector != null && !selector.match(trustedCert)) {
                    if (debug != null) {
                        debug.println("NO - don't try this trustedCert");
                    }
                    continue;
                }
 
                if (debug != null) {
                    debug.println("YES - try this trustedCert");
                    debug.println("anchor.getTrustedCert()."
                        + "getSubjectX500Principal() = "
                        + trustedCert.getSubjectX500Principal());
                }
            } else {
                if (debug != null) {
                    debug.println("PKIXCertPathValidator.engineValidate(): "
                        + "anchor.getTrustedCert() == null");
                }
            }
 
            try {
                return validate(anchor, params);
            } catch (CertPathValidatorException cpe) {
                // remember this exception
                lastException = cpe;
            }
        }
 
        // could not find a trust anchor that verified
        // (a) if we did a validation and it failed, use that exception
        if (lastException != null) {
            throw lastException;
        }
        // (b) otherwise, generate new exception
        throw new CertPathValidatorException
            ("Path does not chain with any of the trust anchors",
             null, null, -1, PKIXReason.NO_TRUST_ANCHOR);
 }
 
 private static PKIXCertPathValidatorResult validate(TrustAnchor anchor,
                                                        ValidatorParams params)
        throws CertPathValidatorException
    {
        int certPathLen = params.certificates().size();
 
        // create PKIXCertPathCheckers
        List certPathCheckers = new ArrayList<>();
        // add standard checkers that we will be using
        certPathCheckers.add(new UntrustedChecker());
        certPathCheckers.add(new AlgorithmChecker(anchor));
        certPathCheckers.add(new KeyChecker(certPathLen,
                                            params.targetCertConstraints()));
        certPathCheckers.add(new ConstraintsChecker(certPathLen));
        PolicyNodeImpl rootNode =
            new PolicyNodeImpl(null, PolicyChecker.ANY_POLICY, null, false,
                               Collections.singleton(PolicyChecker.ANY_POLICY),
                               false);
        PolicyChecker pc = new PolicyChecker(params.initialPolicies(),
                                             certPathLen,
                                             params.explicitPolicyRequired(),
                                             params.policyMappingInhibited(),
                                             params.anyPolicyInhibited(),
                                             params.policyQualifiersRejected(),
                                             rootNode);
        certPathCheckers.add(pc);
        // default value for date is current time
        BasicChecker bc = new BasicChecker(anchor, params.date(),
                                           params.sigProvider(), false);
        certPathCheckers.add(bc);
 
        boolean revCheckerAdded = false;
        List checkers = params.certPathCheckers();
        for (PKIXCertPathChecker checker : checkers) {
            if (checker instanceof PKIXRevocationChecker) {
                if (revCheckerAdded) {
                    throw new CertPathValidatorException(
                        "Only one PKIXRevocationChecker can be specified");
                }
                revCheckerAdded = true;
                // if it's our own, initialize it
                if (checker instanceof RevocationChecker) {
                    ((RevocationChecker)checker).init(anchor, params);
                }
            }
        }
        // only add a RevocationChecker if revocation is enabled and
        // a PKIXRevocationChecker has not already been added
        if (params.revocationEnabled() && !revCheckerAdded) {
            certPathCheckers.add(new RevocationChecker(anchor, params));
        }
        // add user-specified checkers
        certPathCheckers.addAll(checkers);
 
        PKIXMasterCertPathValidator.validate(params.certPath(),
                                             params.certificates(),
                                             certPathCheckers);
 
        return new PKIXCertPathValidatorResult(anchor, pc.getPolicyTree(),
                                               bc.getPublicKey());
 }

上面的函数主要功能是首先对证书链第一个证书进行校验,可能你会感到奇怪,不是拿根证书校验的吗,那是因为我们拿到的这个证书链顺序被倒过来了: 

List certificates() {
            if (certs == null) {
                if (certPath == null) {
                    certs = Collections.emptyList();
                } else {
                    // Reverse the ordering for validation so that the target
                    // cert is the last certificate
                    @SuppressWarnings("unchecked")
                    List xc = new ArrayList<>
                        ((List)certPath.getCertificates());
                    Collections.reverse(xc);
                    certs = xc;
                }
            }
            return certs;
}

上面就是List certList = params.certificates();这个函数的内容,Collections.reverse(xc);就是把证书链顺序倒过来了,所以第一个证书就是证书链的根证书,

这边不知道大家有没有注意到匹配根证书对应系统证书时的selector在设置subject的时候传的是issuerselector.setSubject(firstCert.getIssuerX500Principal());,这个不知道是android系统的bug还是它故意这样的,这样做会导致证书链只要有二级证书就够了,一级证书没有也能校验通过,不过subject毕竟也能改的,所以影响不是很大

 

拿到对应的系统证书后就可以从根证书开始校验了,每个证书需要进行多项校验,每项校验都封装成了一个类,比如KeyChecker,证书链中的每个证书都要过一遍这些check才能算结束,最后调用了

PKIXMasterCertPathValidator.validate函数,我们来看一下它怎么校验的: 

static void validate(CertPath cpOriginal,
                         List reversedCertList,
                         List certPathCheckers)
        throws CertPathValidatorException
    {
        // we actually process reversedCertList, but we keep cpOriginal because
        // we need to return the original certPath when we throw an exception.
        // we will also need to modify the index appropriately when we
        // throw an exception.
 
        int cpSize = reversedCertList.size();
 
        if (debug != null) {
            debug.println("--------------------------------------------------"
                  + "------------");
            debug.println("Executing PKIX certification path validation "
                  + "algorithm.");
        }
 
        for (int i = 0; i < cpSize; i++) {
 
            /* The basic loop algorithm is that we get the
             * current certificate, we verify the current certificate using
             * information from the previous certificate and from the state,
             * and we modify the state for the next loop by setting the
             * current certificate of this loop to be the previous certificate
             * of the next loop. The state is initialized during first loop.
             */
            if (debug != null)
                debug.println("Checking cert" + (i+1) + " ...");
 
            X509Certificate currCert = reversedCertList.get(i);
            Set unresCritExts = currCert.getCriticalExtensionOIDs();
            if (unresCritExts == null) {
                unresCritExts = Collections.emptySet();
            }
 
            if (debug != null && !unresCritExts.isEmpty()) {
                debug.println("Set of critical extensions:");
                for (String oid : unresCritExts) {
                    debug.println(oid);
                }
            }
 
            for (int j = 0; j < certPathCheckers.size(); j++) {
 
                PKIXCertPathChecker currChecker = certPathCheckers.get(j);
                if (debug != null) {
                    debug.println("-Using checker" + (j + 1) + " ... [" +
                        currChecker.getClass().getName() + "]");
                }
 
                if (i == 0)
                    currChecker.init(false);
 
                try {
                    currChecker.check(currCert, unresCritExts);
 
                    if (debug != null) {
                        debug.println("-checker" + (j + 1) +
                            " validation succeeded");
                    }
 
                } catch (CertPathValidatorException cpve) {
                    throw new CertPathValidatorException(cpve.getMessage(),
                        cpve.getCause(), cpOriginal, cpSize - (i + 1),
                        cpve.getReason());
                }
            }
 
            if (!unresCritExts.isEmpty()) {
                throw new CertPathValidatorException("unrecognized " +
                    "critical extension(s)", null, cpOriginal, cpSize-(i+1),
                    PKIXReason.UNRECOGNIZED_CRIT_EXT);
            }
 
            if (debug != null)
                debug.println("\ncert" + (i+1) + " validation succeeded.\n");
        }
 
        if (debug != null) {
            debug.println("Cert path validation succeeded. (PKIX validation "
                          + "algorithm)");
            debug.println("-------------------------------------------------"
                          + "-------------");
        }
}

这边就是我们熟悉的过程了,从根证书开始,拿上一个证书相关内容去校验下一个证书直到最后一个证书,这边注意的是check函数内会把当前证书的public key作为下一个证书校验签名的public key,简单来说就是父证书的Public key会用来校验子证书的签名

 

6、总结

Android 7.0证书校验相对于4.4主要是用设计模式来让校验流程更加清晰,之前一个函数非常长,现在基本不会出现那么长的函数了,不过主要校验规则没太大变化




你可能感兴趣的:(android安全)