First
我们需要了解下到底什么是HTTPS请求,和HTTPS的原理是什么。
HTTP是一种超文本传输协议,
HTTP协议传输的数据都是未加密的,也就是明文的。所以使用HTTP协议传输隐私数据时非常不安全的。
于是网景公司
在1994年
设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS(
Hyper Text Transfer Protocol over Secure Socket Layer
)。
HTTPS的工作原理:
- 客户端输入网址https://www.domain..com,连接到server的443端口。
- 服务器返回一个证书(包含公钥、和证书信息,如证书的颁发机构,过期时间等),证书由服务器所拥有的私钥非对称加密生成。
- 客户端对证书进行验证(首先会验证证书是否有效,比如颁发机构,过期时间等等)。
- 如果客户端验证通过,客户端生成一个随机数,在用服务器返回的证书(公钥)进行加密传输。
- 因为公钥是通过服务器的私钥生成,所以服务器是可以对客户端的传回的加密数据进行对称解密的。服务器拿到由客户端生成的随机数,对要传递的数据使用随机数加密。
- 客户端收到服务器使用随机数加密的数据进行解密。
不过在app的开发中因为我们的app通常只需要和一个服务器端进行交互,所以不必要每次请求都从服务器那边获取证书(公钥),在开发中app直接将服务器对应生成的证书(公钥)放在沙盒中,HTTPS请求时只要直接和服务器返回的证书(公钥)进行比对。如果验证通过则使用公钥进行加密在传递回服务器。
这样即使app中的证书(公钥)被截取,中间人使用证书冒充了服务器与客户端进行通信时(通过了验证),但因为从app返回的数据都是通过证书(公钥)加密的。而中间人从app截取的证书时公钥,缺少对应的私钥即使截获了信息也无法解密。能够最大的程度的保护传递的信息安全。
PS: 从上面的通信过程中,最重要的是存储在服务器的私钥。因为只有私钥生成了在通信过程中传递的证书(公钥),且只有通过私钥才能对公钥加密的信息进行解密,所以在开发过程中保护好私钥的安全。
second
苹果已经封装了HTTPS连接的建立、数据的加密解密功能,我们直接可以访问https网站的,但苹果并没有验证证书是否合法,无法避免中间人攻击。要做到真正安全通讯,需要我们手动去验证服务端返回的证书。
AFNetwork中的AFSecurityPolicy模块主要是用来验证HTTPS请求时证书是否正确。
AFSecurityPolicy封装了证书验证的过程,让用户可以轻易使用,除了去系统信任CA机构列表验证,还支持SSL Pinning方式的验证。
AFSecurityPolicy组成:
其他
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode
验证证书的模式:
AFSSLPinningModeNone:
这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
AFSSLPinningModeCertificate:
这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
AFSSLPinningModePublicKey:
这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。
@property (nonatomic, strong, nullable) NSSet<NSData*> *pinnedCertificates
根据验证模式来返回用于验证服务器的证书。
@property (nonatomic, assign) BOOL allowInvalidCertificates
属性代表是否允许不信任的证书
(证书无效、证书时间过期)通过验证
,默认为NO.
@property (nonatomic, assign) BOOL validatesDomainName
是否验证域名证书的CN(common name)字段。默认值为YES。
从Bundle中获取证书
+ certificatesInBundle:
+ (NSSet<NSData*> *)certificatesInBundle:(NSBundle *)
bundle
当网络框架使用
AFNetworking时,应当使用此方法用来从你的bundle中获取证书。同时如果是调用
+ policyWithPinningMode:withPinnedCertificates:方法来创建
AFSecurityPolicy对象时应当使用这个方法来获得证书
获得默认的安全策略
+ (instancetype)defaultPolicy
返回一个默认的安全策略:不允许使用无效证书、验证域名CN、不验证绑定的证书和公钥。
初始化方法
+ policyWithPinningMode:
+ policyWithPinningMode:withPinnedCertificates:
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)
pinningMode
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet<NSData*> *)pinnedCertificates
以上两个方法都会根据
pinnedCertificates
和
pinningMode
来返回一个
AFSecurityPolicy对象。区别在于前一个方法会根据
NSBundle
*bundle = [
NSBundle
bundleForClass
:[
self
class
]]中的bundle去获取
pinnedCertificates
而后一个方法则是根据
+ certificatesInBundle:方法获得的证书创建的。
验证服务器证书
- (BOOL)evaluateServerTrust:(SecTrustRef)
serverTrust
forDomain:(nullable NSString *)
domain
当服务器响应提出进行证书验证时,此方法将会被调用。然后app根据之前设置的验证策略来进行判断验证是否通过。
下面的代码就是用来判断HTTPS请求的证书验证是否通过。也是AFSecurityPolicy的核心代码
- (
BOOL
)evaluateServerTrust:(
SecTrustRef
)serverTrust
forDomain:( NSString *)domain
forDomain:( NSString *)domain
{
// 当使用自建证书验证域名时,需要使用AFSSLPinningModePublicKey或者AFSSLPinningModeCertificate进行验证
if
(domain &&
self
.
allowInvalidCertificates
&&
self
.
validatesDomainName
&& (
self
.
SSLPinningMode
==
AFSSLPinningModeNone
|| [
self
.
pinnedCertificates
count
] ==
0
)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog ( @"In order to validate a domain name for self signed certificates, you MUST use pinning." );
return NO ;
}
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog ( @"In order to validate a domain name for self signed certificates, you MUST use pinning." );
return NO ;
}
NSMutableArray *policies = [NSMutableArray array];
// 需要验证域名时,需要添加一个验证域名的策略
if
(
self
.
validatesDomainName
) {
[policies addObject :( __bridge_transfer id ) SecPolicyCreateSSL ( true , ( __bridge CFStringRef )domain)];
} else {
[policies addObject :( __bridge_transfer id ) SecPolicyCreateBasicX509 ()];
[policies addObject :( __bridge_transfer id ) SecPolicyCreateSSL ( true , ( __bridge CFStringRef )domain)];
} else {
[policies addObject :( __bridge_transfer id ) SecPolicyCreateBasicX509 ()];
}
//设置验证的策略
SecTrustSetPolicies
(serverTrust, (
__bridge
CFArrayRef
)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//SSLPinningMode为AFSSLPinningModeNone时,allowInvalidCertificates为YES,则代表服务器任何证书都能验证通过;
如果它为NO,则需要判断此服务器证书是否是系统信任的证书
return
self
.
allowInvalidCertificates
||
AFServerTrustIsValid
(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
// 如果服务器证书不是系统信任证书,且不允许不信任的证书通过验证则返回NO
return
NO
;
}
switch ( self . SSLPinningMode ) {
case AFSSLPinningModeNone :
default :
return NO ;
}
switch ( self . SSLPinningMode ) {
case AFSSLPinningModeNone :
default :
return NO ;
case AFSSLPinningModeCertificate: {
//AFSSLPinningModeCertificate是直接将本地的证书设置为信任的根证书,然后来进行判断,并且比较本地证书的内容和
服务器证书内容是否相同,如果有一个相同则返回YES
NSMutableArray
*pinnedCertificates = [
NSMutableArray
array
];
for ( NSData *certificateData in self . pinnedCertificates ) {
[pinnedCertificates addObject :( __bridge_transfer id ) SecCertificateCreateWithData ( NULL , ( __bridge CFDataRef )certificateData)];
for ( NSData *certificateData in self . pinnedCertificates ) {
[pinnedCertificates addObject :( __bridge_transfer id ) SecCertificateCreateWithData ( NULL , ( __bridge CFDataRef )certificateData)];
}
//设置本地的证书为根证书
SecTrustSetAnchorCertificates
(serverTrust, (
__bridge
CFArrayRef
)pinnedCertificates);
//通过本地的证书来判断服务器证书是否可信,不可信,则验证不通过
if
(!
AFServerTrustIsValid
(serverTrust)) {
return NO ;
}
return NO ;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray
*serverCertificates =
AFCertificateTrustChainForServerTrust
(serverTrust);
// 判断本地证书和服务器证书的内容是否相同
for
(
NSData
*trustChainCertificate
in
[serverCertificates
reverseObjectEnumerator
]) {
if ([ self . pinnedCertificates containsObject :trustChainCertificate]) {
return YES ;
}
if ([ self . pinnedCertificates containsObject :trustChainCertificate]) {
return YES ;
}
}
return
NO
;
}
case AFSSLPinningModePublicKey : {
NSUInteger trustedPublicKeyCount = 0 ;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust (serverTrust);
}
case AFSSLPinningModePublicKey : {
NSUInteger trustedPublicKeyCount = 0 ;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust (serverTrust);
//AFSSLPinningModePublicKey是通过比较证书当中公钥(PublicKey)部分来进行验证,
通过SecTrustCopyPublicKey方法获取本地证书和服务器证书,然后进行比较,如果有一个相同,则通过验证
for
(
id
trustChainPublicKey
in
publicKeys) {
for ( id pinnedPublicKey in self . pinnedPublicKeys ) {
if ( AFSecKeyIsEqualToKey (( __bridge SecKeyRef )trustChainPublicKey, ( __bridge SecKeyRef )pinnedPublicKey)) {
trustedPublicKeyCount += 1 ;
}
}
}
return trustedPublicKeyCount > 0 ;
}
}
return NO ;
for ( id pinnedPublicKey in self . pinnedPublicKeys ) {
if ( AFSecKeyIsEqualToKey (( __bridge SecKeyRef )trustChainPublicKey, ( __bridge SecKeyRef )pinnedPublicKey)) {
trustedPublicKeyCount += 1 ;
}
}
}
return trustedPublicKeyCount > 0 ;
}
}
return NO ;
}
AFNetwork使用自签名证书访问参考:
http://blog.csdn.net/daiyelang/article/details/38586475
ps:个人理解可能存在漏误,参考即可。