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

转自 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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容