[转]Android 根证书管理与证书验证(2)

2020-10-27  本文已影响0人  原野大神

转自 https://sq.163yun.com/blog/article/188782427227049984

OpenSSLSocketImpl 的 verifyCertificateChain() 从 sslParameters 获得 X509TrustManager,然后在 Platform.checkServerTrusted() (com.android.org.conscrypt.Platform,位于 external/conscrypt/src/compat/java/org/conscrypt/Platform.java)中执行服务端证书合法有效性的检查:

public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
        String authType, OpenSSLSocketImpl socket) throws CertificateException {
    if (!checkTrusted("checkServerTrusted", tm, chain, authType, Socket.class, socket)
            && !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,
                             socket.getHandshakeSession().getPeerHost())) {
        tm.checkServerTrusted(chain, authType);
    }
}

Platform.checkServerTrusted() 通过执行 X509TrustManager 的 checkServerTrusted() 方法执行证书有合法性检查。

X509TrustManager 来自于 OpenSSLSocketImpl 的 sslParameters,那 sslParameters 又来自于哪里呢?OpenSSLSocketImpl 的 sslParameters 由对象的创建者传入:

public class OpenSSLSocketImpl
extends javax.net.ssl.SSLSocket
implements NativeCrypto.SSLHandshakeCallbacks, SSLParametersImpl.AliasChooser,
SSLParametersImpl.PSKCallbacks {
. . . . . .
private final SSLParametersImpl sslParameters;
. . . . . .
protected OpenSSLSocketImpl(SSLParametersImpl sslParameters) throws IOException {
this.socket = this;
this.peerHostname = null;
this.peerPort = -1;
this.autoClose = false;
this.sslParameters = sslParameters;
}

protected OpenSSLSocketImpl(String hostname, int port, SSLParametersImpl sslParameters)
        throws IOException {
    super(hostname, port);
    this.socket = this;
    this.peerHostname = hostname;
    this.peerPort = port;
    this.autoClose = false;
    this.sslParameters = sslParameters;
}

protected OpenSSLSocketImpl(InetAddress address, int port, SSLParametersImpl sslParameters)
        throws IOException {
    super(address, port);
    this.socket = this;
    this.peerHostname = null;
    this.peerPort = -1;
    this.autoClose = false;
    this.sslParameters = sslParameters;
}


protected OpenSSLSocketImpl(String hostname, int port,
                            InetAddress clientAddress, int clientPort,
                            SSLParametersImpl sslParameters) throws IOException {
    super(hostname, port, clientAddress, clientPort);
    this.socket = this;
    this.peerHostname = hostname;
    this.peerPort = port;
    this.autoClose = false;
    this.sslParameters = sslParameters;
}

protected OpenSSLSocketImpl(InetAddress address, int port,
                            InetAddress clientAddress, int clientPort,
                            SSLParametersImpl sslParameters) throws IOException {
    super(address, port, clientAddress, clientPort);
    this.socket = this;
    this.peerHostname = null;
    this.peerPort = -1;
    this.autoClose = false;
    this.sslParameters = sslParameters;
}

/**
 * Create an SSL socket that wraps another socket. Invoked by
 * OpenSSLSocketImplWrapper constructor.
 */
protected OpenSSLSocketImpl(Socket socket, String hostname, int port,
        boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
    this.socket = socket;
    this.peerHostname = hostname;
    this.peerPort = port;
    this.autoClose = autoClose;
    this.sslParameters = sslParameters;

    // this.timeout is not set intentionally.
    // OpenSSLSocketImplWrapper.getSoTimeout will delegate timeout
    // to wrapped socket
}

也就是说,OpenSSLSocketImpl 的 sslParameters 来自于 javax.net.ssl.SSLSocketFactory,即 OpenSSLSocketFactoryImpl。OpenSSLSocketFactoryImpl 定义(位于 external/conscrypt/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java)如下:

package org.conscrypt;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;

public class OpenSSLSocketFactoryImpl extends javax.net.ssl.SSLSocketFactory {

private final SSLParametersImpl sslParameters;
private final IOException instantiationException;

public OpenSSLSocketFactoryImpl() {
    SSLParametersImpl sslParametersLocal = null;
    IOException instantiationExceptionLocal = null;
    try {
        sslParametersLocal = SSLParametersImpl.getDefault();
    } catch (KeyManagementException e) {
        instantiationExceptionLocal = new IOException("Delayed instantiation exception:");
        instantiationExceptionLocal.initCause(e);
    }
    this.sslParameters = sslParametersLocal;
    this.instantiationException = instantiationExceptionLocal;
}

public OpenSSLSocketFactoryImpl(SSLParametersImpl sslParameters) {
    this.sslParameters = sslParameters;
    this.instantiationException = null;
}

@Override
public String[] getDefaultCipherSuites() {
    return sslParameters.getEnabledCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
    return NativeCrypto.getSupportedCipherSuites();
}

@Override
public Socket createSocket() throws IOException {
    if (instantiationException != null) {
        throw instantiationException;
    }
    return new OpenSSLSocketImpl((SSLParametersImpl) sslParameters.clone());
}

@Override
public Socket createSocket(String hostname, int port) throws IOException, UnknownHostException {
    return new OpenSSLSocketImpl(hostname, port, (SSLParametersImpl) sslParameters.clone());
}

@Override
public Socket createSocket(String hostname, int port, InetAddress localHost, int localPort)
        throws IOException, UnknownHostException {
    return new OpenSSLSocketImpl(hostname,
                                 port,
                                 localHost,
                                 localPort,
                                 (SSLParametersImpl) sslParameters.clone());
}

@Override
public Socket createSocket(InetAddress address, int port) throws IOException {
    return new OpenSSLSocketImpl(address, port, (SSLParametersImpl) sslParameters.clone());
}

@Override
public Socket createSocket(InetAddress address,
                           int port,
                           InetAddress localAddress,
                           int localPort)
        throws IOException {
    return new OpenSSLSocketImpl(address,
                                 port,
                                 localAddress,
                                 localPort,
                                 (SSLParametersImpl) sslParameters.clone());
}

@Override
public Socket createSocket(Socket s, String hostname, int port, boolean autoClose)
        throws IOException {
    return new OpenSSLSocketImplWrapper(s,
                                        hostname,
                                        port,
                                        autoClose,
                                        (SSLParametersImpl) sslParameters.clone());
}

}
OpenSSLSocketImpl 最主要的职责,即是将 SSL/TLS 参数 SSLParametersImpl 与 SSLSocket 粘起来。主要来看默认情况下 SSLParametersImpl 的 X509TrustManager 是什么(位于external/conscrypt/src/main/java/org/conscrypt/SSLParametersImpl.java ):

/**
 * Initializes the parameters. Naturally this constructor is used
 * in SSLContextImpl.engineInit method which directly passes its
 * parameters. In other words this constructor holds all
 * the functionality provided by SSLContext.init method.
 * See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],
 * SecureRandom)} for more information
 */
protected SSLParametersImpl(KeyManager[] kms, TrustManager[] tms,
        SecureRandom sr, ClientSessionContext clientSessionContext,
        ServerSessionContext serverSessionContext, String[] protocols)
        throws KeyManagementException {
    this.serverSessionContext = serverSessionContext;
    this.clientSessionContext = clientSessionContext;

    // initialize key managers
    if (kms == null) {
        x509KeyManager = getDefaultX509KeyManager();
        // There's no default PSK key manager
        pskKeyManager = null;
    } else {
        x509KeyManager = findFirstX509KeyManager(kms);
        pskKeyManager = findFirstPSKKeyManager(kms);
    }

    // initialize x509TrustManager
    if (tms == null) {
        x509TrustManager = getDefaultX509TrustManager();
    } else {
        x509TrustManager = findFirstX509TrustManager(tms);
    }

    // initialize secure random
    // We simply use the SecureRandom passed in by the caller. If it's
    // null, we don't replace it by a new instance. The native code below
    // then directly accesses /dev/urandom. Not the most elegant solution,
    // but faster than going through the SecureRandom object.
    secureRandom = sr;

    // initialize the list of cipher suites and protocols enabled by default
    enabledProtocols = NativeCrypto.checkEnabledProtocols(
            protocols == null ? NativeCrypto.DEFAULT_PROTOCOLS : protocols).clone();
    boolean x509CipherSuitesNeeded = (x509KeyManager != null) || (x509TrustManager != null);
    boolean pskCipherSuitesNeeded = pskKeyManager != null;
    enabledCipherSuites = getDefaultCipherSuites(
            x509CipherSuitesNeeded, pskCipherSuitesNeeded);
}

protected static SSLParametersImpl getDefault() throws KeyManagementException {
    SSLParametersImpl result = defaultParameters;
    if (result == null) {
        // single-check idiom
        defaultParameters = result = new SSLParametersImpl(null,
                                                           null,
                                                           null,
                                                           new ClientSessionContext(),
                                                           new ServerSessionContext(),
                                                           null);
    }
    return (SSLParametersImpl) result.clone();
}

. . . . . .
/**
* @return X.509 trust manager or {@code null} for none.
/
protected X509TrustManager getX509TrustManager() {
return x509TrustManager;
}
. . . . . .
/
*
* Gets the default X.509 trust manager.
* <p>
* TODO: Move this to a published API under dalvik.system.
*/
public static X509TrustManager getDefaultX509TrustManager()
throws KeyManagementException {
X509TrustManager result = defaultX509TrustManager;
if (result == null) {
// single-check idiom
defaultX509TrustManager = result = createDefaultX509TrustManager();
}
return result;
}

private static X509TrustManager createDefaultX509TrustManager()
        throws KeyManagementException {
    try {
        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
        tmf.init((KeyStore) null);
        TrustManager[] tms = tmf.getTrustManagers();
        X509TrustManager trustManager = findFirstX509TrustManager(tms);
        if (trustManager == null) {
            throw new KeyManagementException(
                    "No X509TrustManager in among default TrustManagers: "
                            + Arrays.toString(tms));
        }
        return trustManager;
    } catch (NoSuchAlgorithmException e) {
        throw new KeyManagementException(e);
    } catch (KeyStoreException e) {
        throw new KeyManagementException(e);
    }
}

将 createDefaultX509TrustManager() 的代码复制到我们的应用程序中,就像下面这样:

    private X509TrustManager systemDefaultTrustManager() {
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            return (X509TrustManager) trustManagers[0];
        } catch (GeneralSecurityException e) {
            throw new AssertionError(); // The system has no TLS. Just give up.
        }
    }

在应用程序执行时打断点,借助于 Android Studio 确认系统默认的 X509TrustManager 是什么,不难确认,它是 android.security.net.config.RootTrustManager。android.security.net.config.RootTrustManager 的 checkServerTrusted() 定义(位于 frameworks/base/core/java/android/security/net/config/RootTrustManager.java)如下:

@Override
public void checkServerTrusted(X509Certificate[] certs, String authType, Socket socket)
        throws CertificateException {
    if (socket instanceof SSLSocket) {
        SSLSocket sslSocket = (SSLSocket) socket;
        SSLSession session = sslSocket.getHandshakeSession();
        if (session == null) {
            throw new CertificateException("Not in handshake; no session available");
        }
        String host = session.getPeerHost();
        NetworkSecurityConfig config = mConfig.getConfigForHostname(host);
        config.getTrustManager().checkServerTrusted(certs, authType, socket);
    } else {
        // Not an SSLSocket, use the hostname unaware checkServerTrusted.
        checkServerTrusted(certs, authType);
    }
}

@Override
public void checkServerTrusted(X509Certificate[] certs, String authType, SSLEngine engine)
        throws CertificateException {
    SSLSession session = engine.getHandshakeSession();
    if (session == null) {
        throw new CertificateException("Not in handshake; no session available");
    }
    String host = session.getPeerHost();
    NetworkSecurityConfig config = mConfig.getConfigForHostname(host);
    config.getTrustManager().checkServerTrusted(certs, authType, engine);
}

@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);
}

/**
 * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
 * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
 * modify without modifying those callers.
 */
public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
        String hostname) throws CertificateException {
    if (hostname == null && mConfig.hasPerDomainConfigs()) {
        throw new CertificateException(
                "Domain specific configurations require that the hostname be provided");
    }
    NetworkSecurityConfig config = mConfig.getConfigForHostname(hostname);
    return config.getTrustManager().checkServerTrusted(certs, authType, hostname);
}

NetworkSecurityConfig 的 getTrustManager() 定义(位于 frameworks/base/core/java/android/security/net/config/NetworkSecurityConfig.java)如下:

public NetworkSecurityTrustManager getTrustManager() {
    synchronized(mTrustManagerLock) {
        if (mTrustManager == null) {
            mTrustManager = new NetworkSecurityTrustManager(this);
        }
        return mTrustManager;
    }
}

NetworkSecurityConfig 将管根证书库的组件 SystemCertificateSource 、 UserCertificateSource 和执行证书合法性验证的 NetworkSecurityTrustManager 粘起来:

public static final Builder getDefaultBuilder(int targetSdkVersion) {
    Builder builder = new Builder()
            .setCleartextTrafficPermitted(DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)
            .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
            // System certificate store, does not bypass static pins.
            .addCertificatesEntryRef(
                    new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
    // Applications targeting N and above must opt in into trusting the user added certificate
    // store.
    if (targetSdkVersion <= Build.VERSION_CODES.M) {
        // User certificate store, does not bypass static pins.
        builder.addCertificatesEntryRef(
                new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
    }
    return builder;
}

同时 NetworkSecurityConfig 还提供了一些根据特定条件查找根证书的操作:

public Set<TrustAnchor> getTrustAnchors() {
    synchronized (mAnchorsLock) {
        if (mAnchors != null) {
            return mAnchors;
        }
        // Merge trust anchors based on the X509Certificate.
        // If we see the same certificate in two TrustAnchors, one with overridesPins and one
        // without, the one with overridesPins wins.
        // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first
        // this can be simplified to just using the first occurrence of a certificate.
        Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>();
        for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
            Set<TrustAnchor> anchors = ref.getTrustAnchors();
            for (TrustAnchor anchor : anchors) {
                X509Certificate cert = anchor.certificate;
                if (!anchorMap.containsKey(cert)) {
                    anchorMap.put(cert, anchor);
                }
            }
        }
        ArraySet<TrustAnchor> anchors = new ArraySet<TrustAnchor>(anchorMap.size());
        anchors.addAll(anchorMap.values());
        mAnchors = anchors;
        return mAnchors;
    }
}

. . . . . .
public NetworkSecurityTrustManager getTrustManager() {
synchronized(mTrustManagerLock) {
if (mTrustManager == null) {
mTrustManager = new NetworkSecurityTrustManager(this);
}
return mTrustManager;
}
}

/** @hide */
public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
    for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
        TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);
        if (anchor != null) {
            return anchor;
        }
    }
    return null;
}

/** @hide */
public TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate cert) {
    for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
        TrustAnchor anchor = ref.findByIssuerAndSignature(cert);
        if (anchor != null) {
            return anchor;
        }
    }
    return null;
}

/** @hide */
public Set<X509Certificate> findAllCertificatesByIssuerAndSignature(X509Certificate cert) {
    Set<X509Certificate> certs = new ArraySet<X509Certificate>();
    for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
        certs.addAll(ref.findAllCertificatesByIssuerAndSignature(cert));
    }
    return certs;
}
上一篇下一篇

猜你喜欢

热点阅读