SSL Pinning
1 HTTPS協(xié)議流程
參考:
https://segmentfault.com/a/1190000009002353?sort=newest
https://zhuanlan.zhihu.com/p/353571366
https://juejin.cn/post/6863295544828444686
HTTPS=HTTP+TLS,其它的協(xié)議也類似,如FTPS=FTP+TLS
1) ClientHello
- Client 首先發(fā)送本地的 TLS 版本、支持的加密算法套件,并且生成一個(gè)隨機(jī)數(shù) R1 。
2)Server Hello
- Server 端確認(rèn) TLS 版本號(hào)。從 Client 端支持的加密套件中選取一個(gè),并生成一個(gè)隨機(jī)數(shù) R2 一起發(fā)送給 Client。
- Server 向 Client 發(fā)送自己的CA證書(包含公鑰、證書簽名)。
3)證書校驗(yàn)
- Client 判斷證書簽名與CA證書是否合法有效
- Client 生成隨機(jī)數(shù)pre-master secret,并使用Server發(fā)過來的公鑰對(duì)pre-master secret進(jìn)行加密,將加密后的pre-master secret送給Server。這一步結(jié)束后,Client 與 Server 就都有 R1、R2、pre-master secret 了,兩端便可以使用這 3 個(gè)隨機(jī)數(shù)獨(dú)立生成 對(duì)稱會(huì)話密鑰了,避免了對(duì)稱密鑰的傳輸,同時(shí)可以 根據(jù)會(huì)話密鑰生成 6 個(gè)密鑰(P1~P6) 用作后續(xù)身份驗(yàn)證
Client端和Server端,最終都會(huì)用相同的算法將pre-master secret(預(yù)主密鑰)轉(zhuǎn)換成master secret(主密鑰),通過主密鑰可以生成session key。兩者后續(xù)的通信交互數(shù)據(jù),將通過session key進(jìn)行加密。
參考:https://www.laoqingcai.com/tls1.2-premasterkey/
4)Client 握手結(jié)束通知
- Client 使用 P1 將之前的握手信息的 hash 值加密并發(fā)送給 Server
- Client 發(fā)送握手結(jié)束消息
5)Server 握手結(jié)束通知
- Server 計(jì)算之前的握手信息的 hash 值,并與 P1 解密客戶端發(fā)送的握手信息的 hash 對(duì)比校驗(yàn)
- 驗(yàn)證通過后,使用 P2 將之前的握手信息的 hash 值加密并發(fā)送給 Client
6)Client 開始HTTPS通訊
- Client 計(jì)算之前的握手信息的 hash 值,并與 P2 解密 Server 發(fā)送的握手信息的 hash 對(duì)比校驗(yàn)
- 驗(yàn)證通過后,開始發(fā)起 HTTPS 請(qǐng)求。
兩者后續(xù)的通信交互數(shù)據(jù),將通過session key進(jìn)行加密。所以中間人即使截獲數(shù)據(jù),也無法解析。
2 證書相關(guān)
證書文件
證書=公鑰+(公鑰+元信息)的簽名

其中的元信息包括:
- Subject(主體信息):
- Common Name(CN)通用名稱
- SAN
- Organization
- Organization Unit(OU)
- Country
- State
- City
- Address
- Postal code
- Issuer(簽發(fā)者信息):
- Common Name(CN)通用名稱
- Organization
- Organization Unit(OU)
- Country
- State
- City
- Address
- Postal code
- Validity(有效期):
- Not Before(簽發(fā)日期)
- Not After(過期時(shí)間)
- Signature Algorithm
- Serial Number
- Version
- Extensions(擴(kuò)展信息):只在證書版本2、3中才有
因此,證書的結(jié)構(gòu)大致如下:
CA
簽名 = 計(jì)算摘要 + 對(duì)摘要值私鑰加密
CA:Certificate Authority,專門用自己的私鑰 給別人進(jìn)行簽名的機(jī)構(gòu)
簽發(fā)證書的過程
注意,計(jì)算簽名時(shí),是對(duì)整個(gè)證書文件計(jì)算簽名,也就是對(duì)【元信息+公鑰】計(jì)算簽名,而不只是對(duì)公鑰計(jì)算簽名。
(參考:https://blog.csdn.net/bluishglc/article/details/123617558)
證書的驗(yàn)證過程
關(guān)鍵過程:用信任CA庫里CA證書(公鑰),驗(yàn)證網(wǎng)站的證書文件里的簽名
- 在TLS握手的過程中,客戶端得到了網(wǎng)站的證書
- 客戶端打開證書,查看是哪個(gè)CA簽名的這個(gè)證書
- 在自己信任的CA庫中,找相應(yīng)CA的證書(包含CA的公鑰),
- 用CA證書里面的公鑰解密網(wǎng)站證書上的簽名,取出網(wǎng)站證書的摘要,然后用同樣的算法(比如sha256)算出網(wǎng)站證書的摘要,如果摘要和簽名中的摘要對(duì)的上,說明這個(gè)證書是合法的,且沒被人篡改過
- 讀出里面的CN,對(duì)于網(wǎng)站的證書,里面一般包含的是域名
- 檢查里面的域名和自己訪問網(wǎng)站的域名對(duì)不對(duì)的上,對(duì)的上,就說明這個(gè)證書確實(shí)是頒發(fā)給這個(gè)網(wǎng)站的
- 到此為止檢查通過
證書鏈的驗(yàn)證
參考:
https://www.jianshu.com/p/46e48bc517d0
https://www.cnblogs.com/xiaxveliang/p/13183175.html
我們使用End-user Certificates來確保加密傳輸數(shù)據(jù)的公鑰(public key)不被篡改,而又如何確保end-user certificates的合法性呢?
這個(gè)認(rèn)證過程跟公鑰的認(rèn)證過程類似,首先獲取頒布end-user certificates的CA的證書,然后驗(yàn)證end-user certificates的signature。一般來說,root CAs不會(huì)直接頒布end-user certificates的,而是授權(quán)給多個(gè)二級(jí)CA,而二級(jí)CA又可以授權(quán)給多個(gè)三級(jí)CA,這些中間的CA就是Intermediates CAs,它們才會(huì)頒布end-user certificates。
但是Intermediates Certificates的可靠性又如何保證呢?這就是涉及到證書鏈,Certificate Chain ,鏈?zhǔn)较蛏向?yàn)證證書,直到Root Certificates,如下圖:
中間CA的證書怎么獲?。?br> 以百度的TLS證書進(jìn)行舉例,百度服務(wù)器證書 簽發(fā)者公鑰(中間機(jī)構(gòu)公鑰)通過下圖中的URI獲?。?br>
3 SSL Pinning
參考:
https://shunix.com/ssl-pinning/
https://zhuanlan.zhihu.com/p/58204817
3.1 原理
默認(rèn)情況下,只要網(wǎng)站證書的Root CA,屬于系統(tǒng)信任的Root CA集合(例如,安卓中系統(tǒng)默認(rèn)信任 /system/etc/security/ 中CA證書對(duì)應(yīng)的CA)。
對(duì)于www.example.com,可能出現(xiàn)以下情況:
(1)情況A:
某個(gè)系統(tǒng)信任的Root CA,授權(quán)給了可靠的Intermediate CA 1,Intermediate CA 1給www.example.com頒發(fā)了一個(gè)合法的證書1;
同時(shí)該Root CA也授權(quán)給了不可靠的Intermediate CA 2(不可靠的原因可能是私鑰被泄露),Intermediate CA 2給 www.example.com頒發(fā)了一個(gè)證書2。
這時(shí)候我們希望只信任證書1而不信任證書2,否則一些中間人拿到了證書2,就可以偽裝成合法的www.example.com。
這通過修改信任CA集合是較難實(shí)現(xiàn)的,因?yàn)閮蓚€(gè)證書的根信任錨是相同的Root CA。當(dāng)然,可以從信任集中刪除Root CA,再添加Intermediate CA 1而不添加Intermediate CA 2。但這意味著我們需要移除Root CA。通常,一個(gè)Root CA會(huì)作為成千上萬個(gè)證書的根信任錨,移除Root CA可能引發(fā)過大的影響。
(2)情況B:
系統(tǒng)的可信CA集合被篡改。例如,安卓系統(tǒng)在被Root的情況下,用戶可以修改系統(tǒng)信任證書(方法例如:https://github.com/doug-leith/cydia)。
這種情況下,app可能需要只信任特定的某個(gè)(某些)證書。
原理:
可以采用證書固定。只有當(dāng)網(wǎng)站的證書鏈中,至少有一個(gè)節(jié)點(diǎn)的證書全部?jī)?nèi)容/證書公鑰,跟客戶端預(yù)埋的證書的內(nèi)容相匹配,我們的客戶端才信任此證書鏈。
證書固定 與 限制可信CA 的關(guān)系
如果把某個(gè)Root CA的證書固定起來,那就相當(dāng)于設(shè)置該Root CA為唯一可信的Root CA。
被固定的證書可以是(一般是)某個(gè)中間CA的證書。這樣,不再是所有以trusted Root CA為根的證書鏈都仍舊可信了。只有子節(jié)點(diǎn)包含該中間CA的證書鏈才可信。
被固定的證書的Root CA可以不在系統(tǒng)trusted Root CA集合中。
3.2 實(shí)現(xiàn)方案
具體實(shí)現(xiàn)技術(shù)上,SSL Pinning可以分為Certificate Pinning(證書固定)和Public Key Pinning(公鑰固定)
3.2.1 證書固定
把證書文件打包進(jìn)安裝包,將app設(shè)置為僅接受指定的內(nèi)置證書,而不接受操作系統(tǒng)內(nèi)置的CA根證書對(duì)應(yīng)的任何證書。
3.2.2 公鑰固定
提取證書中的公鑰并內(nèi)置到App中,通過與服務(wù)器對(duì)比公鑰值,來驗(yàn)證連接的合法性。我們?cè)谏暾?qǐng)證書時(shí),公鑰在證書的續(xù)期前后可以保持不變,所以可以解決證書有效期問題。
3.3 實(shí)例
3.3.1 證書固定實(shí)例:基于TrustManagerFactory
// kotlin語法
// 加載證書文件
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val caInput: InputStream = BufferedInputStream(FileInputStream("load-der.crt"))
// 使用CertificateFactory生成一個(gè)X509Certificate的實(shí)例
val ca: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
System.out.println("ca=" + ca.subjectDN)
// 創(chuàng)建一個(gè)KeyStore實(shí)例,并把前邊的X509Certificate實(shí)例加進(jìn)去,并起一個(gè)別名"ca"
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
load(null, null)
setCertificateEntry("ca", ca)
}
// 創(chuàng)建一個(gè)TrustManagerFactory實(shí)例,并且使用前邊的KeyStore實(shí)例進(jìn)行初始化
val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
// 創(chuàng)建一個(gè)SSLContext實(shí)例,并且使用前面的TrustManagerFactory實(shí)例的trustManagers進(jìn)行初始化
val context: SSLContext = SSLContext.getInstance("TLS").apply {
init(null, tmf.trustManagers, null)
}
// 創(chuàng)建HttpsURLConnection實(shí)例urlConnection
val url = URL("https://certs.cac.washington.edu/CAtest/")
val urlConnection = url.openConnection() as HttpsURLConnection
// 將SSLContext實(shí)例context的socketFactory屬性,賦值給urlConnection
urlConnection.sslSocketFactory = context.socketFactory
val inputStream: InputStream = urlConnection.inputStream
copyInputStreamToOutputStream(inputStream, System.out)
3.3.2 證書固定實(shí)例:基于NSC配置文件
需要在Manifest文件的android:networkSecurityConfig屬性加上對(duì)應(yīng)的配置內(nèi)容,示例如下:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Support certificate file, in der or pem format -->
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
</domain-config>
<!-- Support sha256 hash of subject public key -->
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2018-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
</domain-config>
</network-security-config>
關(guān)于NSC的詳細(xì)內(nèi)容,可以參考論文:
[USENIX Sec’21] Why Eve and Mallory Still Love Android: Revisiting TLS (In)Security in Android Applications
或者直接參考Google的官網(wǎng)文檔:
https://developer.android.com/training/articles/security-config
計(jì)劃后續(xù)寫一篇博客詳細(xì)介紹Google Android的NSC。
4 安卓中的SSL Pinning
參考:http://hanpfei.github.io/2018/03/20/android_cert_mgr_and_verify/
SSL Pinning機(jī)制中,客戶端將特定域名的證書與特定的簽發(fā)者綁定。即,對(duì)某個(gè)域名,客戶端只承認(rèn)特定CA為該域名簽發(fā)的證書,而不承認(rèn)其它 CA 為該域名簽發(fā)的證書。
4.1 Android 的根證書管理
AOSP 源碼庫中,CA 根證書主要存放在 system/ca-certificates 目錄下,而在 Android 系統(tǒng)中,則存放在 /system/etc/security/ 目錄下:
cacerts_google 目錄下的根證書,主要用于 system/update_engine、external/libbrillo 和 system/core/crash_reporter 等模塊
cacerts 目錄下的根證書則用于所有的應(yīng)用。cacerts 目錄下的根證書,即 Android 系統(tǒng)的根證書庫,像下面這樣:
它們都是 PEM 格式的 X.509 證書。
Android 系統(tǒng)通過 SystemCertificateSource、DirectoryCertificateSource 和 CertificateSource 等類管理系統(tǒng)根證書庫。
-
CertificateSource定義了可以對(duì)根證書庫執(zhí)行的操作,主要是對(duì)根證書的獲取和查找:
位于frameworks/base/core/java/android/security/net/config/CertificateSource.java -
DirectoryCertificateSource 類提供證書的創(chuàng)建、獲取和查找操作:
位于frameworks/base/core/java/android/security/net/config/DirectoryCertificateSource.java
獲取根證書庫的 getCertificates() 操作在第一次被調(diào)用時(shí),遍歷文件系統(tǒng),并加載系統(tǒng)所有的根證書文件,并緩存起來,以備后面訪問。
根證書的查找操作,主要依據(jù)證書文件的文件名進(jìn)行,證書文件被要求以 [SubjectName 的哈希值].[Index] 的形式命名。 -
SystemCertificateSource 類定義了系統(tǒng)根證書庫的路徑,以及無效一個(gè)根證書的機(jī)制:
位于frameworks/base/core/java/android/security/net/config/SystemCertificateSource.java
Android 系統(tǒng)的根證書位于 /system/etc/security/cacerts/ 目錄下。用戶可以通過將特定根證書復(fù)制到用戶配置目錄的 cacerts-removed 目錄下來無效一個(gè)根證書。
4.2 證書鏈合法性驗(yàn)證
OpenSSLSocketImpl.startHandshake() 通過 NativeCrypto 類的 SSL_do_handshake() 方法執(zhí)行握手操作:
(NativeCrypto 位于external/conscrypt/src/main/java/org/conscrypt/NativeCrypto.java)
SSL_do_handshake() 方法的第三參數(shù)是一個(gè)接口:SSLHandshakeCallbacks
SSLHandshakeCallbacks是NativeCrypto 類定義的接口,其中包含一組回調(diào)函數(shù);這組回調(diào)函數(shù),是SSL_do_handshake() 的參數(shù),在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()的參數(shù):
- 指向一個(gè)sslSession的指針
- X.509 證書鏈
- 認(rèn)證算法名稱
SSLHandshakeCallbacks中的回調(diào)方法的實(shí)現(xiàn)在 OpenSSLSocketImpl 。 OpenSSLSocketImpl中,verifyCertificateChain()的實(shí)現(xiàn)如下:
@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() 中執(zhí)行服務(wù)端證書合法有效性的檢查:
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()會(huì)調(diào)用X509TrustManager.checkServerTrusted()來完成檢查。
其中的X509TrustManager實(shí)例來源于OpenSSLSocketImpl 的sslParameters,如前文所述:
X509TrustManager x509tm = sslParameters.getX509TrustManager();
那OpenSSLSocketImpl 的 sslParameters 又來自于哪里呢?來源于構(gòu)造函數(shù),例如:
protected OpenSSLSocketImpl(SSLParametersImpl sslParameters) throws IOException {
this.socket = this;
this.peerHostname = null;
this.peerPort = -1;
this.autoClose = false;
this.sslParameters = sslParameters;
}
而OpenSSLSocketFactoryImpl類會(huì)實(shí)例化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());
}
}
后面的細(xì)節(jié)暫時(shí)略過不看。
總結(jié):
OpenSSLSocketImpl.startHandshake() 和 NativeCrypto.SSL_do_handshake() 執(zhí)行完整的 SSL/TLS 握手過程。
證書合法性驗(yàn)證是 SSL/TLS 握手的一個(gè)重要步驟。該過程通過 native層調(diào)用Java 層的回調(diào)方法 SSLHandshakeCallbacks.verifyCertificateChain() 來完成。
回調(diào)方法的實(shí)現(xiàn)在OpenSSLSocketImpl。
OpenSSLSocketImpl.verifyCertificateChain()調(diào)用Platform.checkServerTrusted(),調(diào)用RootTrustManager.checkServerTrusted() ,調(diào)用NetworkSecurityTrustManager.checkServerTrusted() ,將真正根據(jù)系統(tǒng)根證書庫執(zhí)行證書合法性驗(yàn)證的 TrustManagerImpl 和 SSL/TLS 握手過程結(jié)合起來。
OpenSSLSocketFactoryImpl 將 OpenSSLSocketImpl 和 SSLParametersImpl 粘起來。
SSLParametersImpl 將 OpenSSLSocketImpl 和 RootTrustManager 粘起來。
NetworkSecurityConfig 將 RootTrustManager 和 NetworkSecurityTrustManager
粘起來。NetworkSecurityConfig、NetworkSecurityTrustManager 和
TrustedCertificateStoreAdapter 將 TrustManagerImpl 和管理系統(tǒng)根證書庫的
SystemCertificateSource 粘起來。
TrustManagerImpl 是證書合法性驗(yàn)證的核心,它會(huì)查找系統(tǒng)根證書庫,并驗(yàn)證服務(wù)端證書的合法性做。
這個(gè)過程的調(diào)用棧如下:
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)
在實(shí)際的開發(fā)過程中,有時(shí)為了節(jié)省昂貴的購買證書的費(fèi)用,而想要自己給自己的服務(wù)器的域名簽發(fā)域名證書,這即是私有 CA 簽名的證書。為了能夠使用這種證書,需要在客戶端預(yù)埋根證書,并對(duì)客戶端證書合法性驗(yàn)證的過程進(jìn)行干預(yù),通過我們預(yù)埋的根證書為服務(wù)端的證書做合法性驗(yàn)證,而不依賴系統(tǒng)的根證書庫。
要想定制 OpenSSLSocketImpl 的證書驗(yàn)證過程,必然要改變 SSLParametersImpl;要改變 OpenSSLSocketImpl 的 SSLParametersImpl,則必然需要修改 SSLSocketFactory。修改 SSLSocketFactory 常常是一個(gè)不錯(cuò)的方法。
兩種實(shí)現(xiàn)手段:
(1)自己實(shí)現(xiàn) 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 雙向認(rèn)證
服務(wù)端也可能校驗(yàn)客戶端的證書(來確??蛻舳耸呛戏ǖ目蛻舳耍?,這種情況下需要把客戶端預(yù)存的證書導(dǎo)入中間人抓包工具中。文章來源:http://www.zghlxwxcb.cn/news/detail-446857.html
可以參考:
https://www.anquanke.com/post/id/272672文章來源地址http://www.zghlxwxcb.cn/news/detail-446857.html
到了這里,關(guān)于【網(wǎng)絡(luò)安全】https與證書原理 | SSL Pinning及其繞過的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!