AFNetwork 3.0 源码解读(五)AFSecurityPolicy


First
我们需要了解下到底什么是HTTPS请求,和HTTPS的原理是什么。

HTTP是一种超文本传输协议, HTTP协议传输的数据都是未加密的,也就是明文的。所以使用HTTP协议传输隐私数据时非常不安全的。 于是网景公司 在1994年 设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS( Hyper Text Transfer Protocol over Secure Socket Layer )。

HTTPS的工作原理:


上图展示了一个网页完整的HTTPS请求的过程。 http://blog.csdn.net/sean_cd/article/details/6966130
  1. 客户端输入网址https://www.domain..com,连接到server的443端口。
  2. 服务器返回一个证书(包含公钥、和证书信息,如证书的颁发机构,过期时间等),证书由服务器所拥有的私钥非对称加密生成。
  3. 客户端对证书进行验证(首先会验证证书是否有效,比如颁发机构,过期时间等等)。
  4. 如果客户端验证通过,客户端生成一个随机数,在用服务器返回的证书(公钥)进行加密传输。
  5. 因为公钥是通过服务器的私钥生成,所以服务器是可以对客户端的传回的加密数据进行对称解密的。服务器拿到由客户端生成的随机数,对要传递的数据使用随机数加密。
  6. 客户端收到服务器使用随机数加密的数据进行解密。

     不过在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
{


    
// 当使用自建证书验证域名时,需要使用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 ;
    }

    NSMutableArray *policies = [NSMutableArray array];
  // 需要验证域名时,需要添加一个验证域名的策略
    if ( self . validatesDomainName ) {
        [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 ;
        case AFSSLPinningModeCertificate: {
           //AFSSLPinningModeCertificate是直接将本地的证书设置为信任的根证书,然后来进行判断,并且比较本地证书的内容和
             服务器证书内容是否相同,如果有一个相同则返回YES
            NSMutableArray *pinnedCertificates = [ NSMutableArray array ];
           
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 ;
            }

            // 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 ;
                }
            }
            return NO ;
        }
       
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 ;
}



AFNetwork使用自签名证书访问参考: http://blog.csdn.net/daiyelang/article/details/38586475


ps:个人理解可能存在漏误,参考即可。

猜你喜欢

转载自blog.csdn.net/yuwuchaio/article/details/50469183