背景
HTTPS当下已经非常普遍,HTTPS全称是Hypertext Transfer Protocol Secure,在HTTP基础上增加了TLS加密,虽然名字里有个Secure,但HTTPS并不是绝对安全的,依然存在被中间人攻击(
Man-in-the-middle attack)的风险,进而导致应用被抓包,HTTPS的加密流量被获取。
MITM中间人攻击
概念
这里举个简单的例子,帮助不了解MITM的读者理解:假如A和B需要通信,这个时候来了一个C,C告诉A自己是B,同时告诉B自己是A,A和B都以为自己在和对方通信,实际上看到的消息都是由C转发的,C就可以在这个过程中完成监听和篡改,这就是中间人攻击。
ARP欺骗
在真实的网络中,要完成上述的过程,需要借助ARP欺骗。ARP是局域网中用IP来查找MAC地址的协议,正常的ARP查找过程中,请求的主机会向局域网发送广播,查询对应IP的MAC地址,局域网的其他主机如果不是这个IP就会忽略请求,对应IP的主机会回应自己的MAC地址。但是ARP协议在机制上就没有考虑校验的情况,只要收到一个ARP回应,主机就会更新自己的ARP表。ARP协议的简单粗暴,让ARP欺骗变得非常简单。攻击者只需要往一个局域网不断发送ARP回应,就能更新各个主机的ARP表,从而达到上面一节说的目的,这个过程也被叫做ARP投毒。当然,更大范围的中间人攻击需要借助DNS投毒,这个就不细说,原理大致类似。
针对HTTPS的MITM
HTTPS在设计上是考虑到了中间人攻击的情况的,TLS是支持双向认证的(一般只需要客户端校验服务端身份),那么为什么还会存在中间人攻击的风险呢?TLS的认证机制是基于证书的,关于证书的细节我们会在后面的篇幅里细讲,这里不展开。我们如果没有信任一些奇奇怪怪的证书,TLS是可以保证通信安全的,否则就会导致TLS的认证机制失效,从而被中间人攻击。
抓包
抓包就是一个MITM的应用场景,这里以常用的Charles为例,看看HTTPS的加密是如何被绕过的。我们知道,如果要解密HTTPS流量,Charles会引导我们给手机安装一个根证书,用文本编辑器打开可以看出是标准的pem格式:
-----BEGIN CERTIFICATE-----
MIIFQDCCBCigAwIBAgIGAXSrxEHXMA0GCSqGSIb3DQEBCwUAMIGkMTUwMwYDVQQD
DCxDaGFybGVzIFByb3h5IENBICgyMCBTZXAgMjAyMCwgQzAyRDM3RUhNRDZSKTEl
MCMGA1UECwwcaHR0cHM6Ly9jaGFybGVzcHJveHkuY29tL3NzbDERMA8GA1UECgwI
WEs3MiBMdGQxETAPBgNVBAcMCEF1Y2tsYW5kMREwDwYDVQQIDAhBdWNrbGFuZDEL
MAkGA1UEBhMCTlowHhcNMDAwMTAxMDAwMDAwWhcNNDkxMTE3MTM0NjM5WjCBpDE1
MDMGA1UEAwwsQ2hhcmxlcyBQcm94eSBDQSAoMjAgU2VwIDIwMjAsIEMwMkQzN0VI
TUQ2UikxJTAjBgNVBAsMHGh0dHBzOi8vY2hhcmxlc3Byb3h5LmNvbS9zc2wxETAP
BgNVBAoMCFhLNzIgTHRkMREwDwYDVQQHDAhBdWNrbGFuZDERMA8GA1UECAwIQXVj
a2xhbmQxCzAJBgNVBAYTAk5aMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAoMCTz31wG8zGwexoelqWd+q9WzQHtkFCReKjw0qRZ/8gjmUuj6pdmEg6FQFr
f9gnIiyPeME+J1gOfIp3z9i860VjviQGUwuPCBuU8G0eXBYZOE2kJvKx1G5QeI/c
hnGi3a3Sk5bGBUV1mMbS35OUkFVgvBygVyEjOF1SKDM/IT9jh5QV8uzhObDk+0F6
mjZ+uug2CDdQLNd4VqMClXrDaFk2gVpcnDatNI0p6doBlsxMedIFw0wPJaXfdl1M
CkPOwqgDpgh4J/3roGwJ5ky9zbE7l552jm/UjTRt5X7608IO5G0Kd5OutvxyqmZU
mYLDS0wcS+vZrPA6WwPUgT+TeQIDAQABo4IBdDCCAXAwDwYDVR0TAQH/BAUwAwEB
/zCCASwGCWCGSAGG+EIBDQSCAR0TggEZVGhpcyBSb290IGNlcnRpZmljYXRlIHdh
cyBnZW5lcmF0ZWQgYnkgQ2hhcmxlcyBQcm94eSBmb3IgU1NMIFByb3h5aW5nLiBJ
ZiB0aGlzIGNlcnRpZmljYXRlIGlzIHBhcnQgb2YgYSBjZXJ0aWZpY2F0ZSBjaGFp
biwgdGhpcyBtZWFucyB0aGF0IHlvdSdyZSBicm93c2luZyB0aHJvdWdoIENoYXJs
ZXMgUHJveHkgd2l0aCBTU0wgUHJveHlpbmcgZW5hYmxlZCBmb3IgdGhpcyB3ZWJz
aXRlLiBQbGVhc2Ugc2VlIGh0dHA6Ly9jaGFybGVzcHJveHkuY29tL3NzbCBmb3Ig
bW9yZSBpbmZvcm1hdGlvbi4wDgYDVR0PAQH/BAQDAgIEMB0GA1UdDgQWBBTtSzIK
BzFSToLLgoAPM4tSPWqDEDANBgkqhkiG9w0BAQsFAAOCAQEAnB+8XuuZAtE3WE03
xIu3rHw+sYdrSvV0es/xt1L2/gnnll/W7PvK4prG62sagblbbnLECLy8AKfN/gh9
aY9i6EXxee+vVy8GC8Cmo4TIv0asmPqUXBv+ggZCRNvnT1mtCvpkjgeEwGTXjqk6
Caq1X61WDzTg/EBPpqhSX10BTFRXLufVMfC/Qy5EdpgwCOm8SZnEwqgAW62GM81L
ngl+WIM+NLX5sdtSmkuhfikNR5rRvFPIjBU1t9qP77l/24Ov5BsGjcMfk3Pjzdqy
8V17WhQGRhb/k6nzlxrxWmQ4rdNVtKLWHD9ubozsX23z6B8l1GMDzYr3VbxdMpGP
V5eiGA==
-----END CERTIFICATE-----
用openssl解析证书:
openssl x509 -text -in ~/Downloads/charles-ssl-proxying-certificate.pem
得到如下输出:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
04:00:00:00:00:01:0f:86:26:e6:0d
Signature Algorithm: sha1WithRSAEncryption
Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
Validity
Not Before: Dec 15 08:00:00 2006 GMT
Not After : Dec 15 08:00:00 2021 GMT
Subject: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:a6:cf:24:0e:be:2e:6f:28:99:45:42:c4:ab:3e:
21:54:9b:0b:d3:7f:84:70:fa:12:b3:cb:bf:87:5f:
c6:7f:86:d3:b2:30:5c:d6:fd:ad:f1:7b:dc:e5:f8:
60:96:09:92:10:f5:d0:53:de:fb:7b:7e:73:88:ac:
52:88:7b:4a:a6:ca:49:a6:5e:a8:a7:8c:5a:11:bc:
7a:82:eb:be:8c:e9:b3:ac:96:25:07:97:4a:99:2a:
07:2f:b4:1e:77:bf:8a:0f:b5:02:7c:1b:96:b8:c5:
b9:3a:2c:bc:d6:12:b9:eb:59:7d:e2:d0:06:86:5f:
5e:49:6a:b5:39:5e:88:34:ec:bc:78:0c:08:98:84:
6c:a8:cd:4b:b4:a0:7d:0c:79:4d:f0:b8:2d:cb:21:
ca:d5:6c:5b:7d:e1:a0:29:84:a1:f9:d3:94:49:cb:
24:62:91:20:bc:dd:0b:d5:d9:cc:f9:ea:27:0a:2b:
73:91:c6:9d:1b:ac:c8:cb:e8:e0:a0:f4:2f:90:8b:
4d:fb:b0:36:1b:f6:19:7a:85:e0:6d:f2:61:13:88:
5c:9f:e0:93:0a:51:97:8a:5a:ce:af:ab:d5:f7:aa:
09:aa:60:bd:dc:d9:5f:df:72:a9:60:13:5e:00:01:
c9:4a:fa:3f:a4:ea:07:03:21:02:8e:82:ca:03:c2:
9b:8f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Subject Key Identifier:
9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl.globalsign.net/root-r2.crl
X509v3 Authority Key Identifier:
keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
Signature Algorithm: sha1WithRSAEncryption
99:81:53:87:1c:68:97:86:91:ec:e0:4a:b8:44:0b:ab:81:ac:
27:4f:d6:c1:b8:1c:43:78:b3:0c:9a:fc:ea:2c:3c:6e:61:1b:
4d:4b:29:f5:9f:05:1d:26:c1:b8:e9:83:00:62:45:b6:a9:08:
93:b9:a9:33:4b:18:9a:c2:f8:87:88:4e:db:dd:71:34:1a:c1:
54:da:46:3f:e0:d3:2a:ab:6d:54:22:f5:3a:62:cd:20:6f:ba:
29:89:d7:dd:91:ee:d3:5c:a2:3e:a1:5b:41:f5:df:e5:64:43:
2d:e9:d5:39:ab:d2:a2:df:b7:8b:d0:c0:80:19:1c:45:c0:2d:
8c:e8:f8:2d:a4:74:56:49:c5:05:b5:4f:15:de:6e:44:78:39:
87:a8:7e:bb:f3:79:18:91:bb:f4:6f:9d:c1:f0:8c:35:8c:5d:
01:fb:c3:6d:b9:ef:44:6d:79:46:31:7e:0a:fe:a9:82:c1:ff:
ef:ab:6e:20:c4:50:c9:5f:9d:4d:9b:17:8c:0c:e5:01:c9:a0:
41:6a:73:53:fa:a5:50:b4:6e:25:0f:fb:4c:18:f4:fd:52:d9:
8e:69:b1:e8:11:0f:de:88:d8:fb:1d:49:f7:aa:de:95:cf:20:
78:c2:60:12:db:25:40:8c:6a:fc:7e:42:38:40:64:12:f7:9e:
81:e1:93:2e
输出中是没有X509v3 Authority Key Identifier的字段的,根据RFC3280的定义,只有根证书允许省略这个字段:
The keyIdentifier field of the authorityKeyIdentifier extension MUST
be included in all certificates generated by conforming CAs to
facilitate certification path construction. There is one exception;
where a CA distributes its public key in the form of a "self-signed"
certificate, the authority key identifier MAY be omitted.
所以这是一个自签名的根证书。当系统信任了根证书,那么根证书链下的所有子证书都会被认为合法,证书链的概念下面细说。Charles的抓包功能是通过中间人攻击来完成的,根证书被信任,那么Charles只要用一个子证书来欺骗被抓包的客户端,就能绕过TLS对中间人攻击的防护。
在Android 6.0之前,用户信任的CA证书会被认为是合法的,安装一个根证书就能解密所有App的HTTPS流量,6.0之后加了个限制,只有系统内置的CA证书被信任,开发中的应用需要手动信任用户添加的CA证书才行,这样就只有手动信任了证书的应用流量能被解密:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
</domain-config>
</network-security-config>
那么有没有在高版本Android上全局抓包的方法呢?还真有,对于ROOT过的手机,我们可以导出.0格式的证书,放到系统的/etc/security/cacerts/目录下,这样就会被认为是系统内置CA证书,从而达到抓取所有应用HTTPS流量的目的。
TLS与证书
在讲本文的主题SSL Pinning之前,需要介绍一些背景知识,下面几节解释了TLS握手过程,以及证书的校验过程。
TLS握手
这里又要拿出Cloudflare的经典图了:
这里描述的是TLS1.3之前的情况,细节不再赘述,可以看我之前的文章:TLS握手过程,我们只需要注意其中一个重点:服务端会把自己的证书发送给客户端,而证书中包含了公钥信息。
证书校验
那么客户端得到证书是如何校验这个证书是合法的呢?这里就要引入证书链的概念,以Google为例:
可以看到,这里一共是有三级证书的,自底向上分别被称为最终实体证书,中间证书和根证书。把三个证书拖出来,分别存成文件:
证书都是cer后缀,我们尝试用DER解码,先看看最终实体证书:
openssl x509 -text -inform der -in ~/Downloads/\*.google.com.cer
输出如下信息:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
31:79:87:25:0f:c0:be:e8:08:00:00:00:00:56:05:ed
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Google Trust Services, CN=GTS CA 1O1
Validity
Not Before: Aug 26 08:08:49 2020 GMT
Not After : Nov 18 08:08:49 2020 GMT
Subject: C=US, ST=California, L=Mountain View, O=Google LLC, CN=*.google.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:8e:14:e9:f8:bb:ae:1f:c4:64:53:b7:d6:7a:76:
50:8b:ab:05:c6:2e:71:32:e0:3e:db:ef:1e:5a:34:
43:a4:74:6a:2b:52:38:75:03:f0:2d:fa:e6:da:82:
10:92:53:9b:a0:0e:28:ea:61:68:2b:0c:6d:df:22:
da:5f:14:1b:90
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
96:65:7B:C2:08:15:03:E1:C3:F8:50:DD:8F:B6:73:65:43:DF:8C:80
X509v3 Authority Key Identifier:
keyid:98:D1:F8:6E:10:EB:CF:9B:EC:60:9F:18:90:1B:A0:EB:7D:09:FD:2B
Authority Information Access:
OCSP - URI:http://ocsp.pki.goog/gts1o1core
CA Issuers - URI:http://pki.goog/gsr2/GTS1O1.crt
X509v3 Subject Alternative Name:
DNS:*.google.com, DNS:*.android.com, DNS:*.appengine.google.com, DNS:*.bdn.dev, DNS:*.cloud.google.com, DNS:*.crowdsource.google.com, DNS:*.datacompute.google.com, DNS:*.g.co, DNS:*.gcp.gvt2.com, DNS:*.gcpcdn.gvt1.com, DNS:*.ggpht.cn, DNS:*.gkecnapps.cn, DNS:*.google-analytics.com, DNS:*.google.ca, DNS:*.google.cl, DNS:*.google.co.in, DNS:*.google.co.jp, DNS:*.google.co.uk, DNS:*.google.com.ar, DNS:*.google.com.au, DNS:*.google.com.br, DNS:*.google.com.co, DNS:*.google.com.mx, DNS:*.google.com.tr, DNS:*.google.com.vn, DNS:*.google.de, DNS:*.google.es, DNS:*.google.fr, DNS:*.google.hu, DNS:*.google.it, DNS:*.google.nl, DNS:*.google.pl, DNS:*.google.pt, DNS:*.googleadapis.com, DNS:*.googleapis.cn, DNS:*.googlecnapps.cn, DNS:*.googlecommerce.com, DNS:*.googlevideo.com, DNS:*.gstatic.cn, DNS:*.gstatic.com, DNS:*.gstaticcnapps.cn, DNS:*.gvt1.com, DNS:*.gvt2.com, DNS:*.metric.gstatic.com, DNS:*.urchin.com, DNS:*.url.google.com, DNS:*.wear.gkecnapps.cn, DNS:*.youtube-nocookie.com, DNS:*.youtube.com, DNS:*.youtubeeducation.com, DNS:*.youtubekids.com, DNS:*.yt.be, DNS:*.ytimg.com, DNS:android.clients.google.com, DNS:android.com, DNS:developer.android.google.cn, DNS:developers.android.google.cn, DNS:g.co, DNS:ggpht.cn, DNS:gkecnapps.cn, DNS:goo.gl, DNS:google-analytics.com, DNS:google.com, DNS:googlecnapps.cn, DNS:googlecommerce.com, DNS:source.android.google.cn, DNS:urchin.com, DNS:www.goo.gl, DNS:youtu.be, DNS:youtube.com, DNS:youtubeeducation.com, DNS:youtubekids.com, DNS:yt.be
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.2
Policy: 1.3.6.1.4.1.11129.2.5.3
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl.pki.goog/GTS1O1core.crl
1.3.6.1.4.1.11129.2.4.2:
.v.....7~.b....a...{7.V..&[...K.ATn...t*........G0E. .i...V.i.U....g..}"..d.6.../R.V+.!..X..#.....S.}..7../.l.V=G....d....GF0M..j-u....~f.HZ
Signature Algorithm: sha256WithRSAEncryption
2f:de:47:43:cd:2d:0a:ed:6f:6d:3c:4b:39:0e:e6:05:17:74:
58:a7:33:f0:a1:10:0a:52:94:55:80:52:8a:5c:a0:88:73:35:
55:cd:d9:51:72:de:c2:96:5c:52:83:f2:ca:05:a1:72:60:06:
8e:da:4d:80:05:6a:60:fe:60:ab:cc:dc:02:67:84:41:47:cd:
eb:af:80:6b:ec:d5:0d:6e:56:5a:bd:00:47:d8:62:2f:4c:01:
93:76:10:bb:16:15:ca:d4:d9:b2:92:0e:5d:96:56:06:95:c3:
a6:d6:77:fb:97:b6:2f:66:06:7c:0c:21:91:ac:8c:84:16:61:
40:02:a9:f1:ca:62:e3:e0:72:da:7b:ab:3f:64:27:bb:d0:ff:
de:a0:c4:6d:a3:72:1d:bc:0e:1d:a7:6a:07:15:69:70:aa:63:
d2:68:ed:50:d2:44:c4:21:ca:b4:ec:73:0b:0c:b2:86:17:fa:
cd:4a:ca:57:2c:56:9d:17:10:0e:68:ce:6d:e1:00:d4:65:f1:
11:63:9f:e4:07:d9:fb:eb:36:7e:77:bc:94:a3:c5:04:8c:ca:
fa:ec:7a:a3:33:fb:b1:65:82:d0:2b:e7:02:29:f9:c4:91:da:
3e:62:3e:8a:da:29:c2:91:bb:60:cf:d6:d2:f4:5b:a5:19:37:
b1:ae:b8:7e
信息量非常大,我们关注几个关键字段:
Signature Algorithm: sha256WithRSAEncryption表示证书的签名是先使用SHA256做摘要,再对摘要做RSA加密生成的。
Public Key Algorithm: id-ecPublicKey,表示公钥的算法是ECDSA,这是一个ECC证书,相比RSA算法,ECDSA的证书更小,运算也更快。
X509v3 Authority Key Identifier,不需要关心内容,有这个字段表示这不是自签名的根证书。
X509v3 Subject Alternative Name,这里包含了证书适用的域名。
最后还有一段Signature Algorithm,这就是证书的签名,配合前面的证书签名算法可以完成证书链的校验。
中间证书的结构大同小异,这里列出用于校验的关键信息:
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:d0:18:cf:45:d4:8b:cd:d3:9c:e4:40:ef:7e:b4:
dd:69:21:1b:c9:cf:3c:8e:4c:75:b9:0f:31:19:84:
3d:9e:3c:29:ef:50:0d:10:93:6f:05:80:80:9f:2a:
a0:bd:12:4b:02:e1:3d:9f:58:16:24:fe:30:9f:0b:
74:77:55:93:1d:4b:f7:4d:e1:92:82:10:f6:51:ac:
0c:c3:b2:22:94:0f:34:6b:98:10:49:e7:0b:9d:83:
39:dd:20:c6:1c:2d:ef:d1:18:61:65:e7:23:83:20:
a8:23:12:ff:d2:24:7f:d4:2f:e7:44:6a:5b:4d:d7:
50:66:b0:af:9e:42:63:05:fb:e0:1c:c4:63:61:af:
9f:6a:33:ff:62:97:bd:48:d9:d3:7c:14:67:dc:75:
dc:2e:69:e8:f8:6d:78:69:d0:b7:10:05:b8:f1:31:
c2:3b:24:fd:1a:33:74:f8:23:e0:ec:6b:19:8a:16:
c6:e3:cd:a4:cd:0b:db:b3:a4:59:60:38:88:3b:ad:
1d:b9:c6:8c:a7:53:1b:fc:bc:d9:a4:ab:bc:dd:3c:
61:d7:93:15:98:ee:81:bd:8f:e2:64:47:20:40:06:
4e:d7:ac:97:e8:b9:c0:59:12:a1:49:25:23:e4:ed:
70:34:2c:a5:b4:63:7c:f9:a3:3d:83:d1:cd:6d:24:
ac:07
Exponent: 65537 (0x10001)
这一段给出了中间证书的公钥,因为是RSA算法,所以这里有一个Modulus和一个Exponent。最终实体证书的签名是用中间证书的私钥对最终实体证书的摘要加密得到,所以对应的解密过程是用中间证书的公钥解密最终实体证书的签名,能得出最终实体证书的摘要,如果能解密成功,就能确认最终实体证书确实是由中间证书签名的,再对最终实体证书做一次摘要,和解密出得摘要比对,如果一致即可确认证书没用被篡改过。
我们手动实现一下这个过程:
已知指数,模和密文,我们需要还原出原文,根据RSA算法,计算原文的算法如下,其中m是明文,c是密文,e是指数,n是模:
m = c ^ e (mod n)
代入上面证书里的值:
>>> n = 0x00d018cf45d48bcdd39ce440ef7eb4dd69211bc9cf3c8e4c75b90f3119843d9e3c29ef500d10936f0580809f2aa0bd124b02e13d9f581624fe309f0b747755931d4bf74de1928210f651ac0cc3b222940f346b981049e70b9d8339dd20c61c2defd1186165e7238320a82312ffd2247fd42fe7446a5b4dd75066b0af9e426305fbe01cc46361af9f6a33ff6297bd48d9d37c1467dc75dc2e69e8f86d7869d0b71005b8f131c23b24fd1a3374f823e0ec6b198a16c6e3cda4cd0bdbb3a4596038883bad1db9c68ca7531bfcbcd9a4abbcdd3c61d7931598ee81bd8fe264472040064ed7ac97e8b9c05912a1492523e4ed70342ca5b4637cf9a33d83d1cd6d24ac07
>>> e = 65537
>>> c = 0x2fde4743cd2d0aed6f6d3c4b390ee605177458a733f0a1100a52945580528a5ca088733555cdd95172dec2965c5283f2ca05a17260068eda4d80056a60fe60abccdc0267844147cdebaf806becd50d6e565abd0047d8622f4c01937610bb1615cad4d9b2920e5d96560695c3a6d677fb97b62f66067c0c2191ac8c8416614002a9f1ca62e3e072da7bab3f6427bbd0ffdea0c46da3721dbc0e1da76a07156970aa63d268ed50d244c421cab4ec730b0cb28617facd4aca572c569d17100e68ce6de100d465f111639fe407d9fbeb367e77bc94a3c5048ccafaec7aa333fbb16582d02be70229f9c491da3e623e8ada29c291bb60cfd6d2f45ba51937b1aeb87e
>>> pow(c, e, n)
986236757547332986472011617696226561292849812918563355472727826767720188564083584387121625107510786855734801053524719833194566624465665316622563244215340671405971599343902468620306327831715457360719532421388780770165778156818229863337344187575566725786793391480600129482653072861971002459947277805295727097226389568776499707662505334062639449916265137796823793276300221537201727072401742985542559596685092673521228140822200236743113743661549252453726123450722876929538747702356573783116197523966334991563351853851212597377279504828784763247643211048750059383511539240076118611220389103205312907651075225923933445
>>> print('0x%x' % pow(c, e, n))
0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d060960864801650304020105000420cd55ba8e69bcc9a1c2aaba552982abd5519051f5708cb6f9885cd3a7d28cd505
最后这一段密文就是最终实体证书的摘要,我们再用openssl手动算一次摘要,看是否一致。先把证书转换成ANS.1格式,这是一种和protobuffer很相似的描述,都是tag, length, value的形式。
openssl asn1parse -inform der -in ~/Downloads/\*.google.com.cer
输出如下:
0:d=0 hl=4 l=2416 cons: SEQUENCE
4:d=1 hl=4 l=2136 cons: SEQUENCE
8:d=2 hl=2 l= 3 cons: cont [ 0 ]
10:d=3 hl=2 l= 1 prim: INTEGER :02
13:d=2 hl=2 l= 16 prim: INTEGER :317987250FC0BEE808000000005605ED
31:d=2 hl=2 l= 13 cons: SEQUENCE
33:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption
44:d=3 hl=2 l= 0 prim: NULL
46:d=2 hl=2 l= 66 cons: SEQUENCE
48:d=3 hl=2 l= 11 cons: SET
50:d=4 hl=2 l= 9 cons: SEQUENCE
52:d=5 hl=2 l= 3 prim: OBJECT :countryName
57:d=5 hl=2 l= 2 prim: PRINTABLESTRING :US
61:d=3 hl=2 l= 30 cons: SET
63:d=4 hl=2 l= 28 cons: SEQUENCE
65:d=5 hl=2 l= 3 prim: OBJECT :organizationName
70:d=5 hl=2 l= 21 prim: PRINTABLESTRING :Google Trust Services
93:d=3 hl=2 l= 19 cons: SET
95:d=4 hl=2 l= 17 cons: SEQUENCE
97:d=5 hl=2 l= 3 prim: OBJECT :commonName
102:d=5 hl=2 l= 10 prim: PRINTABLESTRING :GTS CA 1O1
114:d=2 hl=2 l= 30 cons: SEQUENCE
116:d=3 hl=2 l= 13 prim: UTCTIME :200826080849Z
131:d=3 hl=2 l= 13 prim: UTCTIME :201118080849Z
146:d=2 hl=2 l= 102 cons: SEQUENCE
148:d=3 hl=2 l= 11 cons: SET
150:d=4 hl=2 l= 9 cons: SEQUENCE
152:d=5 hl=2 l= 3 prim: OBJECT :countryName
157:d=5 hl=2 l= 2 prim: PRINTABLESTRING :US
161:d=3 hl=2 l= 19 cons: SET
163:d=4 hl=2 l= 17 cons: SEQUENCE
165:d=5 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
170:d=5 hl=2 l= 10 prim: PRINTABLESTRING :California
182:d=3 hl=2 l= 22 cons: SET
184:d=4 hl=2 l= 20 cons: SEQUENCE
186:d=5 hl=2 l= 3 prim: OBJECT :localityName
191:d=5 hl=2 l= 13 prim: PRINTABLESTRING :Mountain View
206:d=3 hl=2 l= 19 cons: SET
208:d=4 hl=2 l= 17 cons: SEQUENCE
210:d=5 hl=2 l= 3 prim: OBJECT :organizationName
215:d=5 hl=2 l= 10 prim: PRINTABLESTRING :Google LLC
227:d=3 hl=2 l= 21 cons: SET
229:d=4 hl=2 l= 19 cons: SEQUENCE
231:d=5 hl=2 l= 3 prim: OBJECT :commonName
236:d=5 hl=2 l= 12 prim: UTF8STRING :*.google.com
250:d=2 hl=2 l= 89 cons: SEQUENCE
252:d=3 hl=2 l= 19 cons: SEQUENCE
254:d=4 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
263:d=4 hl=2 l= 8 prim: OBJECT :prime256v1
273:d=3 hl=2 l= 66 prim: BIT STRING
341:d=2 hl=4 l=1799 cons: cont [ 3 ]
345:d=3 hl=4 l=1795 cons: SEQUENCE
349:d=4 hl=2 l= 14 cons: SEQUENCE
351:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Key Usage
356:d=5 hl=2 l= 1 prim: BOOLEAN :255
359:d=5 hl=2 l= 4 prim: OCTET STRING [HEX DUMP]:03020780
365:d=4 hl=2 l= 19 cons: SEQUENCE
367:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Extended Key Usage
372:d=5 hl=2 l= 12 prim: OCTET STRING [HEX DUMP]:300A06082B06010505070301
386:d=4 hl=2 l= 12 cons: SEQUENCE
388:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Basic Constraints
393:d=5 hl=2 l= 1 prim: BOOLEAN :255
396:d=5 hl=2 l= 2 prim: OCTET STRING [HEX DUMP]:3000
400:d=4 hl=2 l= 29 cons: SEQUENCE
402:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Subject Key Identifier
407:d=5 hl=2 l= 22 prim: OCTET STRING [HEX DUMP]:041496657BC2081503E1C3F850DD8FB6736543DF8C80
431:d=4 hl=2 l= 31 cons: SEQUENCE
433:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Authority Key Identifier
438:d=5 hl=2 l= 24 prim: OCTET STRING [HEX DUMP]:3016801498D1F86E10EBCF9BEC609F18901BA0EB7D09FD2B
464:d=4 hl=2 l= 104 cons: SEQUENCE
466:d=5 hl=2 l= 8 prim: OBJECT :Authority Information Access
476:d=5 hl=2 l= 92 prim: OCTET STRING [HEX DUMP]:305A302B06082B06010505073001861F687474703A2F2F6F6373702E706B692E676F6F672F677473316F31636F7265302B06082B06010505073002861F687474703A2F2F706B692E676F6F672F677372322F475453314F312E637274
570:d=4 hl=4 l=1218 cons: SEQUENCE
574:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Subject Alternative Name
579:d=5 hl=4 l=1209 prim: OCTET STRING [HEX DUMP]:308204B5820C2A2E676F6F676C652E636F6D820D2A2E616E64726F69642E636F6D82162A2E617070656E67696E652E676F6F676C652E636F6D82092A2E62646E2E64657682122A2E636C6F75642E676F6F676C652E636F6D82182A2E63726F7764736F757263652E676F6F676C652E636F6D82182A2E64617461636F6D707574652E676F6F676C652E636F6D82062A2E672E636F820E2A2E6763702E677674322E636F6D82112A2E67637063646E2E677674312E636F6D820A2A2E67677068742E636E820E2A2E676B65636E617070732E636E82162A2E676F6F676C652D616E616C79746963732E636F6D820B2A2E676F6F676C652E6361820B2A2E676F6F676C652E636C820E2A2E676F6F676C652E636F2E696E820E2A2E676F6F676C652E636F2E6A70820E2A2E676F6F676C652E636F2E756B820F2A2E676F6F676C652E636F6D2E6172820F2A2E676F6F676C652E636F6D2E6175820F2A2E676F6F676C652E636F6D2E6272820F2A2E676F6F676C652E636F6D2E636F820F2A2E676F6F676C652E636F6D2E6D78820F2A2E676F6F676C652E636F6D2E7472820F2A2E676F6F676C652E636F6D2E766E820B2A2E676F6F676C652E6465820B2A2E676F6F676C652E6573820B2A2E676F6F676C652E6672820B2A2E676F6F676C652E6875820B2A2E676F6F676C652E6974820B2A2E676F6F676C652E6E6C820B2A2E676F6F676C652E706C820B2A2E676F6F676C652E707482122A2E676F6F676C656164617069732E636F6D820F2A2E676F6F676C65617069732E636E82112A2E676F6F676C65636E617070732E636E82142A2E676F6F676C65636F6D6D657263652E636F6D82112A2E676F6F676C65766964656F2E636F6D820C2A2E677374617469632E636E820D2A2E677374617469632E636F6D82122A2E67737461746963636E617070732E636E820A2A2E677674312E636F6D820A2A2E677674322E636F6D82142A2E6D65747269632E677374617469632E636F6D820C2A2E75726368696E2E636F6D82102A2E75726C2E676F6F676C652E636F6D82132A2E776561722E676B65636E617070732E636E82162A2E796F75747562652D6E6F636F6F6B69652E636F6D820D2A2E796F75747562652E636F6D82162A2E796F7574756265656475636174696F6E2E636F6D82112A2E796F75747562656B6964732E636F6D82072A2E79742E6265820B2A2E7974696D672E636F6D821A616E64726F69642E636C69656E74732E676F6F676C652E636F6D820B616E64726F69642E636F6D821B646576656C6F7065722E616E64726F69642E676F6F676C652E636E821C646576656C6F706572732E616E64726F69642E676F6F676C652E636E8204672E636F820867677068742E636E820C676B65636E617070732E636E8206676F6F2E676C8214676F6F676C652D616E616C79746963732E636F6D820A676F6F676C652E636F6D820F676F6F676C65636E617070732E636E8212676F6F676C65636F6D6D657263652E636F6D8218736F757263652E616E64726F69642E676F6F676C652E636E820A75726368696E2E636F6D820A7777772E676F6F2E676C8208796F7574752E6265820B796F75747562652E636F6D8214796F7574756265656475636174696F6E2E636F6D820F796F75747562656B6964732E636F6D820579742E6265
1792:d=4 hl=2 l= 33 cons: SEQUENCE
1794:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Certificate Policies
1799:d=5 hl=2 l= 26 prim: OCTET STRING [HEX DUMP]:30183008060667810C010202300C060A2B06010401D679020503
1827:d=4 hl=2 l= 51 cons: SEQUENCE
1829:d=5 hl=2 l= 3 prim: OBJECT :X509v3 CRL Distribution Points
1834:d=5 hl=2 l= 44 prim: OCTET STRING [HEX DUMP]:302A3028A026A0248622687474703A2F2F63726C2E706B692E676F6F672F475453314F31636F72652E63726C
1880:d=4 hl=4 l= 260 cons: SEQUENCE
1884:d=5 hl=2 l= 10 prim: OBJECT :1.3.6.1.4.1.11129.2.4.2
1896:d=5 hl=3 l= 245 prim: OCTET STRING [HEX DUMP]:0481F200F0007600B21E05CC8BA2CD8A204E8766F92BB98A2520676BDAFA70E7B249532DEF8B905E000001742A06F9BD000004030047304502205BB262C173701DC2F4D182C34760FA693875B409B650DA2DBE966D80CB6EE9C8022100CFD52D39644158ED44F23ABE9B4746304D8CAB6A2D75DA92F0187E6688485A0D007600E712F2B0377E1A62FB8EC90C6184F1EA7B37CB561D11265BF3E0F34BF241546E000001742A06F9D2000004030047304502200B69DB8E9756FB698955FA04BF8467C80E7D22C2F364CD36DACDD72F52D1562B022100925882AA2314AAB3009F53A47D93CE377FCB2FCA6C1E563D4716ACEBF264E087
2144:d=1 hl=2 l= 13 cons: SEQUENCE
2146:d=2 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption
2157:d=2 hl=2 l= 0 prim: NULL
2159:d=1 hl=4 l= 257 prim: BIT STRING
再看看RFC5280的定义:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
第二行就是TBSCertificate的位置和长度,真正用来计算摘要的部分,偏移是4,hl=4表示header长度是4,l=2136表示内容长度是2136,总长度是2140,我们用dd提取出这部分,并对输出结果做SHA256摘要:
dd if=/Users/shunix/Downloads/\*.google.com.cer of=/Users/shunix/tmp/tbs skip=4 bs=1 count=2140
openssl dgst -sha256 ~/tmp/tbs
输出如下:
SHA256(/Users/shunix/tmp/tbs)= cd55ba8e69bcc9a1c2aaba552982abd5519051f5708cb6f9885cd3a7d28cd505
而上面我们用公钥解出来的摘要去掉前面的
0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d060960864801650304020105000420
最后结果也是cd55ba8e69bcc9a1c2aaba552982abd5519051f5708cb6f9885cd3a7d28cd505,二者完全一致,说明最终实体证书没有被篡改过。
而中间证书和根证书的校验也是同样的过程,这里就不赘述。根证书是自签名的,甚至都可以不用签名,因为根证书是内置到系统里的。
CA之所以不直接从根证书发布最终实体证书是因为这样风险太大,一旦出现错误发布容易导致根证书不受信任,所以CA会从根证书发布中间证书,再从中间证书发布最终实体证书,完成自我隔离。当然,根证书也是有不被信任的先例的,比如Firefox和Chrome在2015年移除了对CNNIC根证书的信任,具体原因就不细讲了。
SSL PINNING
综上所述,有几种情况SSL是无法保证安全的:
- 某些wifi或者网站要求装个根证书才能继续使用,比如某订票网站
- 被信任的CA随意发布证书,比如赛门铁克,17年居然发布了example.com的证书,挺缺德的
所以客户端提供了一种额外的机制来保证HTTPS通信的安全,SSL Pinning,SSL Pinning又可以细分为Certificate Pinning和Public Key Pinning。
CERTIFICATE PINNING
Certificate Pinning也就是证书锁定,简单来说就是把证书文件打包进安装包,通过加载本地证书自定义TrustManager,进而创建自定义的SSLSocketFactory来完成的,这里贴一些关键代码:
fun loadCertificate(): Certificate {
// 假设证书放在assets下
return BaseApplication.instance.assets
.open("shunix.cert").use { input ->
CertificateFactory.getInstance("X.509").generateCertificate(input)
}
}
fun getTrustManager(): TrustManager {
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
setCertificateEntry("shunix", loadCertificate())
}
return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).run {
init(keyStore)
trustManagers[0]
}
}
fun getSSLSocketFactory(): SSLSocketFactory {
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, arrayOf(getTrustManager()), null)
}
return sslContext.socketFactory
}
FALLBACK策略
内置证书的方案存在一个问题,证书是会过期的,一般最终实体证书也就是一年的有效期,而中间证书和根证书有效期都是10年左右,超过绝大多数应用的生命周期了,所以一个比较简单的做法是同时内置中间证书或者根证书作为子证书过期的应对方案。中间证书和根证书一般都是同一个机构的,所以选哪一个并没有本质的区别,但是这样在安全性上会有一定的妥协,可以参考前面提到的赛门铁克和CNNIC证书,当根证书不被信任,一样会出问题,还有就是如果更换证书的CA也是做不到的,局限性很大。
那么能不能只内置最终实体证书,同时能解决有效期的问题呢?很自然地会想到动态更新本地证书,那就带来了另一个问题,如何保证本地证书更新的安全性呢?这是一个鸡生蛋蛋生鸡的问题,其实不讲究点,更新证书的请求可以直接走没有证书锁定的HTTPS完成,因为HTTPS被劫持本身就是小概率情况。如果追求完美的话,有一个比较麻烦的解决方案,就是再打包一个自签名的证书到安装包,锁定这个自签名的证书来更新最终实体证书,自签名证书有效期可以自定义,定的足够长就好,当然服务端也需要做相应改造,是否需要这么严格的安全策略就看业务场景了。
PUBLIC KEY PINNING
Certificate Pinning实现过于繁琐,同时局限性比较大,所以就有了锁定Subject Public Key Info的实现。申请过证书的都知道,需要提供算法和公钥,即使更换新证书,这两个东西也是可以保持不变的,Android 7.0以上提供了非常方便的实现,只需要在res/xml/network_security_config.xml里加如下配置:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<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>
可以参考Android官方文档:https://developer.android.com/training/articles/security-config#CertificatePinning
这里pin的是Base64编码后的Subject Public Key Info(SPKI)的哈希,具体生成方法可以参考Mozilla的文档:https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning#Extracting_the_Base64_encoded_public_key_information
FALLBACK策略
虽然基于公钥的锁定不存在证书过期的问题,但依然需要fallback策略,因为可能存在私钥泄漏的情况下,这种情况下需要重新发布证书,公钥私钥都会改变。Android允许pin多个SPKI,只要符合一个就能正常通信,所以这里可以pin几个CA的根证书或者中间证书的SPKI,会损失一点安全性,但是为私钥泄漏的情况留下了操作空间。
结语
即使做了SSL Pinning,依然有HTTPS被劫持,内容被篡改的可能,比如对于证书锁定的方式,可以直接替换掉apk里的证书,再做重打包,对于公钥锁定的方式,可以通过hook RootTrustManager的方式直接绕过验证。
服务端需要在业务上对客户端请求做验证,只依靠机制上的安全是不够的,道高一尺魔高一丈,客户端和服务端应该互不信任,对于输入需要做足够的校验,这并不是一种overhead。
作者:SHUNIX
出处:https://shunix.com/ssl-pinning/
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/71691.html