【网络安全】https与证书原理 | SSL Pinning及其绕过

SSL Pinning

1 HTTPS协议流程

参考:
https://segmentfault.com/a/1190000009002353?sort=newest
https://zhuanlan.zhihu.com/p/353571366
https://juejin.cn/post/6863295544828444686

HTTPS=HTTP+TLS,其它的协议也类似,如FTPS=FTP+TLS
【网络安全】https与证书原理 | SSL Pinning及其绕过_第1张图片
1) ClientHello

  • Client 首先发送本地的 TLS 版本、支持的加密算法套件,并且生成一个随机数 R1 。

2)Server Hello

  • Server 端确认 TLS 版本号。从 Client 端支持的加密套件中选取一个,并生成一个随机数 R2 一起发送给 Client。
  • Server 向 Client 发送自己的CA证书(包含公钥、证书签名)。

3)证书校验

  • Client 判断证书签名与CA证书是否合法有效
  • Client 生成随机数pre-master secret,并使用Server发过来的公钥对pre-master secret进行加密,将加密后的pre-master secret送给Server。这一步结束后,Client 与 Server 就都有 R1、R2、pre-master secret 了,两端便可以使用这 3 个随机数独立生成 对称会话密钥了,避免了对称密钥的传输,同时可以 根据会话密钥生成 6 个密钥(P1~P6) 用作后续身份验证

Client端和Server端,最终都会用相同的算法将pre-master secret(预主密钥)转换成master secret(主密钥),通过主密钥可以生成session key。两者后续的通信交互数据,将通过session key进行加密。
【网络安全】https与证书原理 | SSL Pinning及其绕过_第2张图片
参考:https://www.laoqingcai.com/tls1.2-premasterkey/

4)Client 握手结束通知

  • Client 使用 P1 将之前的握手信息的 hash 值加密并发送给 Server
  • Client 发送握手结束消息

5)Server 握手结束通知

  • Server 计算之前的握手信息的 hash 值,并与 P1 解密客户端发送的握手信息的 hash 对比校验
  • 验证通过后,使用 P2 将之前的握手信息的 hash 值加密并发送给 Client

6)Client 开始HTTPS通讯

  • Client 计算之前的握手信息的 hash 值,并与 P2 解密 Server 发送的握手信息的 hash 对比校验
  • 验证通过后,开始发起 HTTPS 请求。

两者后续的通信交互数据,将通过session key进行加密。所以中间人即使截获数据,也无法解析。

2 证书相关

证书文件

证书=公钥+(公钥+元信息)的签名

【网络安全】https与证书原理 | SSL Pinning及其绕过_第3张图片

其中的元信息包括:

  • Subject(主体信息):
    • Common Name(CN)通用名称
    • SAN
    • Organization
    • Organization Unit(OU)
    • Country
    • State
    • City
    • Address
    • Postal code
  • Issuer(签发者信息):
    • Common Name(CN)通用名称
    • Organization
    • Organization Unit(OU)
    • Country
    • State
    • City
    • Address
    • Postal code
  • Validity(有效期):
    • Not Before(签发日期)
    • Not After(过期时间)
  • Signature Algorithm
  • Serial Number
  • Version
  • Extensions(扩展信息):只在证书版本2、3中才有

因此,证书的结构大致如下:
【网络安全】https与证书原理 | SSL Pinning及其绕过_第4张图片

CA

签名 = 计算摘要 + 对摘要值私钥加密
CA:Certificate Authority,专门用自己的私钥 给别人进行签名的机构

签发证书的过程

注意,计算签名时,是对整个证书文件计算签名,也就是对【元信息+公钥】计算签名,而不只是对公钥计算签名。
【网络安全】https与证书原理 | SSL Pinning及其绕过_第5张图片
(参考:https://blog.csdn.net/bluishglc/article/details/123617558)

证书的验证过程

关键过程:用信任CA库里CA证书(公钥),验证网站的证书文件里的签名

  1. 在TLS握手的过程中,客户端得到了网站的证书
  2. 客户端打开证书,查看是哪个CA签名的这个证书
  3. 在自己信任的CA库中,找相应CA的证书(包含CA的公钥),
  4. CA证书里面的公钥解密网站证书上的签名,取出网站证书的摘要,然后用同样的算法(比如sha256)算出网站证书的摘要,如果摘要和签名中的摘要对的上,说明这个证书是合法的,且没被人篡改过
  5. 读出里面的CN,对于网站的证书,里面一般包含的是域名
  6. 检查里面的域名和自己访问网站的域名对不对的上,对的上,就说明这个证书确实是颁发给这个网站的
  7. 到此为止检查通过

证书链的验证

参考:
https://www.jianshu.com/p/46e48bc517d0
https://www.cnblogs.com/xiaxveliang/p/13183175.html

我们使用End-user Certificates来确保加密传输数据的公钥(public key)不被篡改,而又如何确保end-user certificates的合法性呢?

这个认证过程跟公钥的认证过程类似,首先获取颁布end-user certificates的CA的证书,然后验证end-user certificates的signature。一般来说,root CAs不会直接颁布end-user certificates的,而是授权给多个二级CA,而二级CA又可以授权给多个三级CA,这些中间的CA就是Intermediates CAs,它们才会颁布end-user certificates。

但是Intermediates Certificates的可靠性又如何保证呢?这就是涉及到证书链,Certificate Chain ,链式向上验证证书,直到Root Certificates,如下图:

【网络安全】https与证书原理 | SSL Pinning及其绕过_第6张图片
中间CA的证书怎么获取?
以百度的TLS证书进行举例,百度服务器证书 签发者公钥(中间机构公钥)通过下图中的URI获取:
【网络安全】https与证书原理 | SSL Pinning及其绕过_第7张图片

3 SSL Pinning

参考:
https://shunix.com/ssl-pinning/
https://zhuanlan.zhihu.com/p/58204817

3.1 原理

默认情况下,只要网站证书的Root CA,属于系统信任的Root CA集合(例如,安卓中系统默认信任 /system/etc/security/ 中CA证书对应的CA)。

对于www.example.com,可能出现以下情况:

(1)情况A:

某个系统信任的Root CA,授权给了可靠的Intermediate CA 1,Intermediate CA 1给www.example.com颁发了一个合法的证书1;

同时该Root CA也授权给了不可靠的Intermediate CA 2(不可靠的原因可能是私钥被泄露),Intermediate CA 2给 www.example.com颁发了一个证书2。

这时候我们希望只信任证书1而不信任证书2,否则一些中间人拿到了证书2,就可以伪装成合法的www.example.com。

这通过修改信任CA集合是较难实现的,因为两个证书的根信任锚是相同的Root CA。当然,可以从信任集中删除Root CA,再添加Intermediate CA 1而不添加Intermediate CA 2。但这意味着我们需要移除Root CA。通常,一个Root CA会作为成千上万个证书的根信任锚,移除Root CA可能引发过大的影响。

(2)情况B:

系统的可信CA集合被篡改。例如,安卓系统在被Root的情况下,用户可以修改系统信任证书(方法例如:https://github.com/doug-leith/cydia)。

这种情况下,app可能需要只信任特定的某个(某些)证书。

原理:

可以采用证书固定。只有当网站的证书链中,至少有一个节点的证书全部内容/证书公钥,跟客户端预埋的证书的内容相匹配,我们的客户端才信任此证书链。

证书固定 与 限制可信CA 的关系

如果把某个Root CA的证书固定起来,那就相当于设置该Root CA为唯一可信的Root CA。

被固定的证书可以是(一般是)某个中间CA的证书。这样,不再是所有以trusted Root CA为根的证书链都仍旧可信了。只有子节点包含该中间CA的证书链才可信。

被固定的证书的Root CA可以不在系统trusted Root CA集合中。

3.2 实现方案

具体实现技术上,SSL Pinning可以分为Certificate Pinning(证书固定)和Public Key Pinning(公钥固定)

3.2.1 证书固定

把证书文件打包进安装包,将app设置为仅接受指定的内置证书,而不接受操作系统内置的CA根证书对应的任何证书。

3.2.2 公钥固定

提取证书中的公钥并内置到App中,通过与服务器对比公钥值,来验证连接的合法性。我们在申请证书时,公钥在证书的续期前后可以保持不变,所以可以解决证书有效期问题。

3.3 实例

3.3.1 证书固定实例:基于TrustManagerFactory
// kotlin语法

// 加载证书文件
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val caInput: InputStream = BufferedInputStream(FileInputStream("load-der.crt"))

// 使用CertificateFactory生成一个X509Certificate的实例
val ca: X509Certificate = caInput.use {
    cf.generateCertificate(it) as X509Certificate
}
System.out.println("ca=" + ca.subjectDN)


// 创建一个KeyStore实例,并把前边的X509Certificate实例加进去,并起一个别名"ca"
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
    load(null, null)
    setCertificateEntry("ca", ca)
}

// 创建一个TrustManagerFactory实例,并且使用前边的KeyStore实例进行初始化
val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
    init(keyStore)
}

// 创建一个SSLContext实例,并且使用前面的TrustManagerFactory实例的trustManagers进行初始化
val context: SSLContext = SSLContext.getInstance("TLS").apply {
    init(null, tmf.trustManagers, null)
}

// 创建HttpsURLConnection实例urlConnection
val url = URL("https://certs.cac.washington.edu/CAtest/")
val urlConnection = url.openConnection() as HttpsURLConnection

// 将SSLContext实例context的socketFactory属性,赋值给urlConnection
urlConnection.sslSocketFactory = context.socketFactory

val inputStream: InputStream = urlConnection.inputStream
copyInputStreamToOutputStream(inputStream, System.out)
3.3.2 证书固定实例:基于NSC配置文件

需要在Manifest文件的android:networkSecurityConfig属性加上对应的配置内容,示例如下:


<network-security-config>
	
    <domain-config>
        <domain includeSubdomains="true">example.comdomain>
        <trust-anchors>
            <certificates src="@raw/my_ca"/>
        trust-anchors>
    domain-config>
    
    
    <domain-config>
        <domain includeSubdomains="true">example.comdomain>
        <pin-set expiration="2018-01-01">
            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=pin>
            
            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=pin>
        pin-set>
    domain-config>
network-security-config>

关于NSC的详细内容,可以参考论文:
[USENIX Sec’21] Why Eve and Mallory Still Love Android: Revisiting TLS (In)Security in Android Applications

或者直接参考Google的官网文档:
https://developer.android.com/training/articles/security-config

计划后续写一篇博客详细介绍Google Android的NSC。

4 安卓中的SSL Pinning

参考:http://hanpfei.github.io/2018/03/20/android_cert_mgr_and_verify/

SSL Pinning机制中,客户端将特定域名的证书与特定的签发者绑定。即,对某个域名,客户端只承认特定CA为该域名签发的证书,而不承认其它 CA 为该域名签发的证书。

4.1 Android 的根证书管理

AOSP 源码库中,CA 根证书主要存放在 system/ca-certificates 目录下,而在 Android 系统中,则存放在 /system/etc/security/ 目录下:

【网络安全】https与证书原理 | SSL Pinning及其绕过_第8张图片
cacerts_google 目录下的根证书,主要用于 system/update_engine、external/libbrillo 和 system/core/crash_reporter 等模块

cacerts 目录下的根证书则用于所有的应用。cacerts 目录下的根证书,即 Android 系统的根证书库,像下面这样:
【网络安全】https与证书原理 | SSL Pinning及其绕过_第9张图片
它们都是 PEM 格式的 X.509 证书。

Android 系统通过 SystemCertificateSource、DirectoryCertificateSource 和 CertificateSource 等类管理系统根证书库。

  • CertificateSource定义了可以对根证书库执行的操作,主要是对根证书的获取和查找
    位于frameworks/base/core/java/android/security/net/config/CertificateSource.java

  • DirectoryCertificateSource 类提供证书的创建、获取和查找操作:
    位于frameworks/base/core/java/android/security/net/config/DirectoryCertificateSource.java
    获取根证书库的 getCertificates() 操作在第一次被调用时,遍历文件系统,并加载系统所有的根证书文件,并缓存起来,以备后面访问。
    根证书的查找操作,主要依据证书文件的文件名进行,证书文件被要求以 [SubjectName 的哈希值].[Index] 的形式命名。

  • SystemCertificateSource 类定义了系统根证书库的路径,以及无效一个根证书的机制:
    位于frameworks/base/core/java/android/security/net/config/SystemCertificateSource.java
    Android 系统的根证书位于 /system/etc/security/cacerts/ 目录下。用户可以通过将特定根证书复制到用户配置目录的 cacerts-removed 目录下来无效一个根证书。

4.2 证书链合法性验证

OpenSSLSocketImpl.startHandshake() 通过 NativeCrypto 类SSL_do_handshake() 方法执行握手操作:

(NativeCrypto 位于external/conscrypt/src/main/java/org/conscrypt/NativeCrypto.java)

SSL_do_handshake() 方法的第三参数是一个接口:SSLHandshakeCallbacks
【网络安全】https与证书原理 | SSL Pinning及其绕过_第10张图片

SSLHandshakeCallbacks是NativeCrypto 类定义的接口,其中包含一组回调函数;这组回调函数,是SSL_do_handshake() 的参数,在SSL_do_handshake() 中被传入native层

SSLHandshakeCallbacks 中的方法之一是verifyCertificateChain():

/**
 * A collection of callbacks from the native OpenSSL code that are
 * related to the SSL handshake initiated by SSL_do_handshake.
 */
public interface SSLHandshakeCallbacks {
    /**
     * Verify that we trust the certificate chain is trusted.
     *
     * @param sslSessionNativePtr pointer to a reference of the SSL_SESSION
     * @param certificateChainRefs chain of X.509 certificate references
     * @param authMethod auth algorithm name
     *
     * @throws CertificateException if the certificate is untrusted
     */
    public void verifyCertificateChain(long sslSessionNativePtr, 
    		long[] certificateChainRefs,
    		String authMethod) 
    		throws CertificateException;

verifyCertificateChain()的参数:

  • 指向一个sslSession的指针
  • X.509 证书链
  • 认证算法名称

SSLHandshakeCallbacks中的回调方法的实现在 OpenSSLSocketImpl 。 OpenSSLSocketImpl中,verifyCertificateChain()的实现如下:

@SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks
@Override
public void verifyCertificateChain(long sslSessionNativePtr, long[] certRefs, String authMethod)
        throws CertificateException {
    try {
        X509TrustManager x509tm = sslParameters.getX509TrustManager();
        if (x509tm == null) {
            throw new CertificateException("No X.509 TrustManager");
        }
        if (certRefs == null || certRefs.length == 0) {
            throw new SSLException("Peer sent no certificate");
        }
        OpenSSLX509Certificate[] peerCertChain = new OpenSSLX509Certificate[certRefs.length];
        for (int i = 0; i < certRefs.length; i++) {
            peerCertChain[i] = new OpenSSLX509Certificate(certRefs[i]);
        }
        // Used for verifyCertificateChain callback
        handshakeSession = new OpenSSLSessionImpl(sslSessionNativePtr, null, peerCertChain,
                getHostnameOrIP(), getPort(), null);
        boolean client = sslParameters.getUseClientMode();
        if (client) {
            Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this);
            if (sslParameters.isCTVerificationEnabled(getHostname())) {
                byte[] tlsData = NativeCrypto.SSL_get_signed_cert_timestamp_list(
                                    sslNativePointer);
                byte[] ocspData = NativeCrypto.SSL_get_ocsp_response(sslNativePointer);
                CTVerifier ctVerifier = sslParameters.getCTVerifier();
                CTVerificationResult result =
                    ctVerifier.verifySignedCertificateTimestamps(peerCertChain, tlsData, ocspData);
                if (result.getValidSCTs().size() == 0) {
                    throw new CertificateException("No valid SCT found");
                }
            }
        } else {
            String authType = peerCertChain[0].getPublicKey().getAlgorithm();
            Platform.checkClientTrusted(x509tm, peerCertChain, authType, this);
        }
    } catch (CertificateException e) {
        throw e;
    } catch (Exception e) {
        throw new CertificateException(e);
    } finally {
        // Clear this before notifying handshake completed listeners
        handshakeSession = null;
    }
}

这里面,verifyCertificateChain() 从 OpenSSLSocketImpl的 sslParameters 获得 X509TrustManager:

 X509TrustManager x509tm = sslParameters.getX509TrustManager();

然后在 Platform.checkServerTrusted() 中执行服务端证书合法有效性的检查:

Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this);

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,如前文所述:

 X509TrustManager x509tm = sslParameters.getX509TrustManager();

那OpenSSLSocketImpl 的 sslParameters 又来自于哪里呢?来源于构造函数,例如:

protected OpenSSLSocketImpl(SSLParametersImpl sslParameters) throws IOException {
    this.socket = this;
    this.peerHostname = null;
    this.peerPort = -1;
    this.autoClose = false;
    this.sslParameters = sslParameters;
}

而OpenSSLSocketFactoryImpl类会实例化OpenSSLSocketImpl:

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;

    …………

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

}

后面的细节暂时略过不看。

总结:

OpenSSLSocketImpl.startHandshake() 和 NativeCrypto.SSL_do_handshake() 执行完整的 SSL/TLS 握手过程。

证书合法性验证是 SSL/TLS 握手的一个重要步骤。该过程通过 native层调用Java 层的回调方法 SSLHandshakeCallbacks.verifyCertificateChain() 来完成。

回调方法的实现在OpenSSLSocketImpl。

OpenSSLSocketImpl.verifyCertificateChain()调用Platform.checkServerTrusted(),调用RootTrustManager.checkServerTrusted() ,调用NetworkSecurityTrustManager.checkServerTrusted() ,将真正根据系统根证书库执行证书合法性验证的 TrustManagerImpl 和 SSL/TLS 握手过程结合起来。

OpenSSLSocketFactoryImpl 将 OpenSSLSocketImpl 和 SSLParametersImpl 粘起来。

SSLParametersImpl 将 OpenSSLSocketImpl 和 RootTrustManager 粘起来。

NetworkSecurityConfig 将 RootTrustManager 和 NetworkSecurityTrustManager
粘起来。

NetworkSecurityConfig、NetworkSecurityTrustManager 和
TrustedCertificateStoreAdapter 将 TrustManagerImpl 和管理系统根证书库的
SystemCertificateSource 粘起来。

TrustManagerImpl 是证书合法性验证的核心,它会查找系统根证书库,并验证服务端证书的合法性做。

这个过程的调用栈如下:

com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted()
android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted()
android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted()
android.security.net.config.RootTrustManager.checkServerTrusted()
com.android.org.conscrypt.Platform.checkServerTrusted()
com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain()
com.android.org.conscrypt.NativeCrypto.SSL_do_handshake()
com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake()
com.android.okhttp.Connection.connectTls()

4.3 自定义证书(SSL Pinning)

在实际的开发过程中,有时为了节省昂贵的购买证书的费用,而想要自己给自己的服务器的域名签发域名证书,这即是私有 CA 签名的证书。为了能够使用这种证书,需要在客户端预埋根证书,并对客户端证书合法性验证的过程进行干预,通过我们预埋的根证书为服务端的证书做合法性验证,而不依赖系统的根证书库。

要想定制 OpenSSLSocketImpl 的证书验证过程,必然要改变 SSLParametersImpl;要改变 OpenSSLSocketImpl 的 SSLParametersImpl,则必然需要修改 SSLSocketFactory。修改 SSLSocketFactory 常常是一个不错的方法。

两种实现手段:
(1)自己实现 X509TrustManager
像下面这样:

private final class HelloX509TrustManager implements X509TrustManager {
    private X509TrustManager mSystemDefaultTrustManager;
    private X509Certificate mCertificate;
    private HelloX509TrustManager() {
        mCertificate = loadRootCertificate();
        mSystemDefaultTrustManager = systemDefaultTrustManager();
    }
    private X509Certificate loadRootCertificate() {
        String certName = "netease.crt";
        X509Certificate certificate = null;
        InputStream certInput = null;
        try {
            certInput = new BufferedInputStream(MainActivity.this.getAssets().open(certName));
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            certificate = (X509Certificate) certificateFactory.generateCertPath(certInput).getCertificates().get(0);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } finally {
            if (certInput != null) {
                try {
                    certInput.close();
                } catch (IOException e) {
                }
            }
        }
        return certificate;
    }
    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.
        }
    }
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        mSystemDefaultTrustManager.checkClientTrusted(chain, authType);
    }
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        for (X509Certificate certificate : chain) {
            try {
                certificate.verify(mCertificate.getPublicKey());
                return;
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (NoSuchProviderException e) {
                e.printStackTrace();
            } catch (SignatureException e) {
                e.printStackTrace();
            }
        }
        mSystemDefaultTrustManager.checkServerTrusted(chain, authType);
    }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return mSystemDefaultTrustManager.getAcceptedIssuers();
    }
}

(2)仅修改 X509TrustManager 所用的根证书库

private TrustManager[] createX509TrustManager() {
    CertificateFactory cf = null;
    InputStream in = null;
    TrustManager[] trustManagers = null
    try {
        cf = CertificateFactory.getInstance("X.509");
        in = getAssets().open("ca.crt");
        Certificate ca = cf.generateCertificate(in);
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(null, null);
        keystore.setCertificateEntry("ca", ca);
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keystore);
        trustManagers = tmf.getTrustManagers();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (IOException e1) {
        e1.printStackTrace();
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return trustManagers;
}

4.4 双向认证

服务端也可能校验客户端的证书(来确保客户端是合法的客户端),这种情况下需要把客户端预存的证书导入中间人抓包工具中。

可以参考:
https://www.anquanke.com/post/id/272672

你可能感兴趣的:(安卓安全,常识,web安全)