Apple 自ios9发布之后就一直建议大家启用https,曾经一度要求在2015年下半年要求必须启用https,否则会被拒审核。但是后来因为考虑整个行业还没准备好(也可能是为了配合国家网络监控...)放弃了这一强制做法。
最近国际版需要启用https来访问所有接口,对该问题进行了一点研究,发现网上很多教程不写上如何获取对应域名证书的获取过程,以及大家在启用https后不知道如何设置charles进行debug调试。这里做一下记录:
1、获取到站点的证书:
我们可以使用以下openssl命令来获取到服务器的公开二进制证书(以google为例):
openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer
冒号中的为命令主要部分。该条命令将会在当前路径下,形成google.com站点的公开二进制证书,命名为https.cer。您可以将www.google.com 替换成您自己的站点以此来获取您自己站点的https.cer。
需要注意的是 针对某些站点 openssl s_client -connect www.google.com:443 会失效,以国际版域名为例:
grants-MacBook-Pro:Downloads grant.zhou$ openssl s_client -connect www.himalaya.com:443
CONNECTED(00000003)
4479151596:error:140790E5:SSL routines:ssl23_write:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 307 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1569321137
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
国际版网站明明是支持https的,但是这里检查出来却是不支持https,这是为什么呢?最终,我发现这种行为的原因是服务器需要SNI(服务器名称TLS扩展)才能正常工作。
Initially, SSL and TLS were designed to support only one web site per IP endpoint (address and port combination). SNI is a TLS extension that enables use of more than one certificate on the same IP endpoint. TLS clients use the extension to send the desired name, and TLS servers use it to select the correct certificate to respond with. In a nutshell, SNI makes virtual secure hosting possible.
简单的说SNI扩展是允许一个ip绑定多个证书,通常用于虚拟主机,一个物理主机可以虚拟化为多个域名下的不同服务器。
将-servername选项提供给openssl使其成功连接:
grants-MacBook-Pro:~ grant.zhou$ openssl s_client -connect api.himalaya.com:443 -servername api.himalaya.com
CONNECTED(00000003)
depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority
verify return:1
depth=2 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2
verify return:1
depth=1 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = http://certs.godaddy.com/repository/, CN = Go Daddy Secure Certificate Authority - G2
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "CarGlass, Inc.", CN = *.himalaya.com
verify return:1
---
Certificate chain
0 s:/C=US/ST=California/L=San Francisco/O=CarGlass, Inc./CN=*.himalaya.com
i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2
1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2
i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2
2 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2
i:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority
3 s:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority
i:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIG4TCCBcmgAwIBAgIJAOzIz6nohTXJMA0GCSqGSIb3DQEBCwUAMIG0MQswCQYD
VQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEa
MBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xLTArBgNVBAsTJGh0dHA6Ly9jZXJ0
cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzEzMDEGA1UEAxMqR28gRGFkZHkgU2Vj
dXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTE4MDYwNjAxNDM0NVoX
DTIwMDYwNjAxNDM0NVowbDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
aWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xFzAVBgNVBAoTDkNhckdsYXNzLCBJ
bmMuMRcwFQYDVQQDDA4qLmhpbWFsYXlhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAOftD0btfns0CPv12kALHnwMiwetLKKfCq5sDiBqaBpbiMRk
Pm86l2qIpni78Q+ntTNS5/EUEp6qgYvQ/42YDxO8nugP92sekm9KkYdK6i5wYQsf
B0HwJ/IoFrHFvWanCg5+fG2M+vUQ+JUUcDKhxWc49rs62fpup973pOaEbETjYEkm
s3pbe81qp28K0niSFsl4jvmyHlUbJhaQvC328kIZCi9RkBRxHfVuFvVv+ISw7wN7
ksEtif0BNmuZsC7QZgYrWcpB0Wu0Bhe0/7eNmg3pwRZlrAvAiy1dChVAP20vmhmu
SJdOmpFGwLLlormLwvSel3GJjZwN8Z+8ITgOt6kCAwEAAaOCAzswggM3MAwGA1Ud
EwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA4GA1UdDwEB
/wQEAwIFoDA1BgNVHR8ELjAsMCqgKKAmhiRodHRwOi8vY3JsLmdvZGFkZHkuY29t
L2dkaWcyczItOS5jcmwwXQYDVR0gBFYwVDBIBgtghkgBhv1tAQcXAjA5MDcGCCsG
AQUFBwIBFitodHRwOi8vY2VydGlmaWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRv
cnkvMAgGBmeBDAECAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6
Ly9vY3NwLmdvZGFkZHkuY29tLzBABggrBgEFBQcwAoY0aHR0cDovL2NlcnRpZmlj
YXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkaWcyLmNydDAfBgNVHSMEGDAW
gBRAwr0njsw0gzCiM9f7bLPwtCyAzjAnBgNVHREEIDAegg4qLmhpbWFsYXlhLmNv
bYIMaGltYWxheWEuY29tMB0GA1UdDgQWBBSmyRJiqB93KnNKgMB3g4RNyr3YUzCC
AX8GCisGAQQB1nkCBAIEggFvBIIBawFpAHYApLkJkLQYWBSHuxOizGdwCjw1mAT5
G9+443fNDsgN3BAAAAFj0sPOmgAABAMARzBFAiBYG6B7sRlmuIATazT/Z9Cyl9Ve
8arTBZw3UAzuh8MmrwIhANgUZ9LrXfMMCDIJ5UA5hlzS5AO4WQI8l8ejpDnY3jrh
AHYA7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/csAAAFj0sPTeQAABAMA
RzBFAiBABzkFtWA4HGPhK8bvcBCwv5Y8TB7VgJtGvySoeQzomwIhAIpJPlKjM/kH
Het1cD8sS5Adn1W/+HP/NxwPYRxkWayVAHcAXqdz+d9WwOe1Nkh90EngMnqRmgyE
oRIShBh1loFxRVgAAAFj0sPUQQAABAMASDBGAiEA+RTnuAJQA17YU5ClZpxrcZYW
oUsyXOdAP6/BBayIT7gCIQC7Ixv0use3LffPXPF70AKRoaqovFZi/8+yC/10JPvR
QzANBgkqhkiG9w0BAQsFAAOCAQEAd8b00/8YLy1adejHoh04CpjmqDtSQWYz3Z7l
na+Tt+wk6XYwfr4VP5QdMglCQDfA7GMbM39EW0PE6sNdmGnvfiPIg4BDHL6e4y62
+osalclSkKmalp1E3mEul3Ox+H0iHFc+AKStYdqmyLGjawzehPvGvRSD1PsTYqoT
Km2Z+6+T/PJ/xVzTCnKkstrkMqIE1aFxAtr6ZNB6aNgSG2x5d4j1OrRnDVDinPnO
zXDvb9c2ox15meAH9yLlmDeJoQB6ySuP4b2KIyL2BekPfBSK2ayN2V7pWBMwNhmA
Os4XnyqXm5KeFV4VlxcTn92Y8pEVUCwLg6Wzk9n8SpSCxerjxA==
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=San Francisco/O=CarGlass, Inc./CN=*.himalaya.com
issuer=/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2
---
No client certificate CA names sent
Peer signing digest: SHA256
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5861 bytes and written 458 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: 88EC4FC760304DB6CAC5302F7DC9F623B4CD5C6A57727026C1B918537B02635B
Session-ID-ctx:
Master-Key: 53249292D50A3BCC4B78FA6825EC8BE4168F28448F38D0CF379BD17E299D8F144D347227395511A3FFB8650799E014AC
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 49 64 4c 4a 54 62 65 35-77 46 52 6b 4a 54 54 7a IdLJTbe5wFRkJTTz
0010 - 51 33 0e 7f 39 5e 60 0b-ee fe 32 a3 10 b0 e4 17 Q3..9^`...2.....
0020 - e0 a0 e8 f5 82 91 55 d0-29 9f aa a9 1c ea b9 99 ......U.).......
0030 - 60 a4 fd ca a8 29 13 30-4a f0 a5 87 50 c0 45 f1 `....).0J...P.E.
0040 - e1 69 11 f7 40 c5 32 de-74 8d 63 d4 cb 43 b2 4d .i..@.2.t.c..C.M
0050 - f6 46 10 8f 4d 61 47 db-fc a7 36 8e 21 82 62 3b .F..MaG...6.!.b;
0060 - 19 92 ba 28 7f c8 58 24-ca de 46 d7 bb dc 40 a3 ...(..X$..F...@.
0070 - 6b 0f 13 8f d0 f6 4f 23-88 62 8d 6d 26 ff 50 48 k.....O#.b.m&.PH
0080 - c1 4d f8 76 c1 3c 07 29-84 21 b1 d5 05 b3 b3 f6 .M.v.<.).!......
0090 - 58 f3 97 74 0c 24 40 d6-a0 b7 10 e0 40 da bb 8e X..t.$@.....@...
00a0 - 2c 89 fd 20 03 f8 2e 8b-9a 7c 40 dd 06 97 bb f7 ,.. .....|@.....
Start Time: 1569313654
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
大家也可以在上面的返回看到 -----BEGIN CERTIFICATE----- 和 -----END CERTIFICATE----- 就是himalaya.com https访问时候的公钥,通过 shell openssl x509 -outform DER > https.cer
我们可以转换为Xcode支持的证书类型。
2、将证书放进我们的XCode项目工程中:
如上图所示,将我们的https.cer拖到我们的工程Supporting Files中,把 Copy Items if needed 的勾选上。然后把您的Add to targets 选上,点击确定。就完成了证书的导入工作。
3、在我们的代码中使用我们的cer
AFNetworking中的AFSecurityPolicy是主要的类,我们可以这样来使用它(AFNetworking 2.6.0之前):
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"https" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init];
[securityPolicy setAllowInvalidCertificates:NO];
[securityPolicy setPinnedCertificates:@[certData]];
[securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate];
[securityPolicy setValidatesDomainName:YES];
[securityPolicy setValidatesCertificateChain:NO];
manager.securityPolicy = securityPolicy;
解析:
1)新建一个manager, 地球人都知道
2)在mainBundle中寻找我们刚才拖进项目中的https.cer, 并且将相关的数据读取出来
3)新建一个AFSecurityPolicy,并进行相应的配置
4)将这个AFSecurityPolicy 实例赋值给manager
也可以这样来使用:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
[securityPolicy setAllowInvalidCertificates:NO];
[securityPolicy setValidatesDomainName:YES];
[securityPolicy setValidatesCertificateChain:NO];
manager.securityPolicy = securityPolicy;
这种方式比前面那种方式要更加简便一些,主要原因在于AFNetworking会自动去搜索mainBundle下的所有cer结尾的文件并放进内存中;再一一对比。因此在代码中可以省略不写。
这样一个网络请求的https的安全策略就配置好了,接下来再说明一下几个AFSecurityPolicy相关的配置
1> SSLPinningMode
SSLPinningMode 定义了https连接时,如何去校验服务器端给予的证书。
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
AFSSLPinningModeNone: 代表客户端无条件地信任服务器端返回的证书。
AFSSLPinningModePublicKey: 代表客户端会将服务器端返回的证书与本地保存的证书中,PublicKey的部分进行校验;如果正确,才继续进行。
AFSSLPinningModeCertificate: 代表客户端会将服务器端返回的证书和本地保存的证书中的所有内容,包括PublicKey和证书部分,全部进行校验;如果正确,才继续进行。
2> allowInvalidCertificates
allowInvalidCertificates 定义了客户端是否信任非法证书。一般来说,每个版本的iOS设备中,都会包含一些既有的CA根证书。如果接收到的证书是iOS信任的CA根证书签名的,那么则为合法证书;否则则为“非法”证书。
allowInvalidCertificates 就是用来确认是否信任这样的证书的。当然,我们也可以给iOS加入新的信任的CA证书。iOS已有的CA根证书,可以在这里了解到:https://support.apple.com/en-us/HT204132
3> pinnedCertificates
pinnedCertificates 就是用来校验服务器返回证书的证书。通常都保存在mainBundle 下。通常默认情况下,AFNetworking会自动寻找在mainBundle的根目录下所有的.cer文件并保存在pinnedCertificates数组里,以校验服务器返回的证书。
4> validatesDomainName
validatesDomainName 是指是否校验在证书中的domain这一个字段。每个证书都会包含一个DomainName, 它可以是一个IP地址,一个域名或者一端带有通配符的域名。如*.google.com, www.google.com 都可以成为这个证书的DomainName。设置validatesDomainName=YES将严格地保证其安全性。
5> validatesCertificateChain
validatesCertificateChain 指的是是否校验其证书链。
通常来讲,一个CA证书颁发机构有很多个子机构,用来签发不同用途的子证书,然后这些子证书又再用来签发相应的证书。只有证书链上的证书都正确,CertificateChain才算验证完成。以Google为例:
从上图可以看到,Google.com的证书的根CA证书是GeoTrust Global CA; 而CA并没有直接给google.com签证书,而是先签名了Google Internet Authority G2, 然后G2再签名了google.com。这时候就需要设备中保存有Google Internet Authority G2证书才能通过校验。
一般来讲,我推荐将validatesCertificateChain设置为NO,因为并不是太有必要做CertificateChain的校验。并且,在AFNetworking 2.6.0中,也正式将validatesCertificateChain拿掉了(https://github.com/AFNetworking/AFNetworking/blob/master/CHANGELOG.md), 其原因也同样为:There was no documented security advantage to pinning against an entire certificate chain。
因此,在2.6.0之后,可以不管这个字段。而在此之前,从效率上来说,设定为NO会是个比较明智的选择。
做好以上工作后,您应该就可以正常访问您自己的https服务器了。如果还是有问题请检查:
(1)、HTTPS服务器的正确配置。一般来说,可以使用浏览器打开相同页面来查看浏览器上的小锁是否正常。
(2)、是否https.cer正确打包进了项目中。查看第2步中的内容。
(3)、其他。跟帖呗。有问题大家一起交流,共同进步:)
参考文档:
1、https://github.com/AFNetworking/AFNetworking/blob/master/CHANGELOG.md
2、http://nelson.logdown.com/posts/2015/04/29/how-to-properly-setup-afnetworking-security-connection/
3、https://www.jianshu.com/p/4102b817ff2f