美文网首页
iOS https认证

iOS https认证

作者: TerryD | 来源:发表于2019-03-01 13:58 被阅读8次

iOS https认证

项目背景

最近在做iOS 热更新,出于公司信息安全限制没使用JSPatch平台来下发js,而是把js放自己的对象存储服务器上。为了简单处理,js路径里加了对应的版本号。这样对应版本的app会去下载该js,能下载到表示该版本有patch,没有则表示无patch。既然没有接入JSPatch平台来下发,自然存在下发js安全的问题。比如对象存储服务器被劫持后,黑客篡改js,就可能造成全部版本不可用的风险。最简单的就是让对象存储服务器走https,简单暴力。

https简单理解

https是简单的理解为http secure,就是安全的http。我们知道http是应用层协议,它紧接着的一层为运输层(TCP/UDP)。那如何做到安全的http,且不影响原本的http协议,也不影响TCP或者UDP呢?加一层,也就是SSL(Secure Socket Layer)。SSL3.0以后开始叫TSL了,最新的是TSL1.3,不过主流支持到TSL1.2。https具体原理不说了,有点繁琐,具体百度。只要知道客户端和服务端在握手阶段商量出了一个非对称秘钥(也就是两个不一样的秘钥,可以互相解密对方加密的内容,但是比较耗时)。然后用这个非对称秘钥加密传输一个秘钥,让彼此知道这个秘钥,接下来就可以有用这个秘钥来加密需要传输的数据了(也就是对称加解密)。还是用图(网图)来说明吧:

https原理图

https防中间人攻击以及利用

通常说https可以防中间人攻击,那么怎么做到防中间人攻击呢?怎么做到A与B通信就是A与B,而不是A与C,不是C与B,甚至是A通过C与B通信?简单说给他们各自一个凭证,A证证明A是A,B证证明B就是B。他们在开始通信的时候先亮出彼此的证书,A验证后发现确实与我通信的就是B,B验证后与我通信的就是A才开始接下里的通信。好了,大家说我平时访问https的网站或者网址,都没用到啥https证书,这有啥用?其实我们一直在用,只是他们隐匿的深一点。当我们在chrome里键入:https://www.baidu.com 的时候,浏览器里地址栏会出现一个小锁的图标,如下图:

图片

这表示该网站的https证书是CA(可以理解为给你发身份证的公安局)认证过的,可以放心使用了。那怎么认证的呢?浏览器和操作系统都会内置一些权威CA的证书(MBP里打开钥匙串,选择系统根证书,可有看到目前所有的根证书,选择证书可以看到我们添加的信任的证书),

图片

在访问的时候这些网站时候,把网站亮出的证书与内置的权威证书对比下,如果有一个命中,就表示认证通过(其实验证的是一个证书链)。所以不要随便把一些未知证书导入系统里,这样也会是潜在安全隐患。因为一旦你导入系统并信任,那么它就具有系统内置权威CA证书一样的功能了。大家常用的抓包工具Charles就是这样的原理(严格意义上也算中间人攻击,只是这个中间人是我们自己)。开启了Charles后,需要你安装Charles的证书到系统,不然是无法拦截到并看到明文https请求的。下面图中可以看到打开Charles,百度的证书签发机构变成Charles了:

图片

这就表明与我们浏览器通信的其实是Charles这个中间人。之所以能看到百度网址的内容,是因为Charles这个中间人访问百度并将内容返回了给我们浏览器。这也是为什么Charles可以看到https请求的response是明文而不是乱码的原因。

iOS中的https认证

鉴于越来越多的安全事故,3年前苹果要求所有的App都要配置ATS开关。如果不配置,默认所有http请求都打不开而且所有验证不通过的https请求也打不开。也是逼着公司,开发者重视安全,重视用户隐私并且尽早接入https。但是可能阻力太大,苹果额外加了个允许任意请求的开关,这样开发者就可以绕开苹果的要求了。不过对于这个下发js的项目背景来说,认证是必须的。鉴于AFNetworking(后面简称AF)基本上是iOS网络库的事实标准,下面以AF里认证为例说明。

AF里做证书验证的主要类是AFSecurityPolicy,负责网络请求的AFHTTPSessionManager有个该类的实例属性,用于作证书认证。打开AFSecurityPolicy的m文件,发现里面的注释都比较清楚,这里只单独说两个属性:

@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>

/**
 The criteria by which server trust should be evaluated against the pinned SSL certificates. Defaults to `AFSSLPinningModeNone`.
 */
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;

/**
 The certificates used to evaluate server trust according to the SSL pinning mode. 

  By default, this property is set to any (`.cer`) certificates included in the target compiling AFNetworking. Note that if you are using AFNetworking as embedded framework, no certificates will be pinned by default. Use `certificatesInBundle` to load certificates from your target, and then create a new policy by calling `policyWithPinningMode:withPinnedCertificates`.
 
 Note that if pinning is enabled, `evaluateServerTrust:forDomain:` will return true if any pinned certificate matches.
 */
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;

AFSSLPinningMode枚举定义如下:

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,//对于https请求我们不用作验证,丢给系统做,也就是去比对系统内置证书
    AFSSLPinningModePublicKey,//该模式要求app提供证书,会比较证书对应的公钥是否一致
    AFSSLPinningModeCertificate,//该模式有要求app提供证书,会比较整个完整证书,也就是验证策略最严格
};

上面3种枚举的验证策略注释已经说的很明确了,可以看出AFSSLPinningModeNone适合网站或者后台部署了权威CA签发的https证书。AFSSLPinningModePublicKey和AFSSLPinningModeCertificate就对应我们自签名的https证书了。既然自签名证书,自然需要我们提供证书了,也就是把证书放在工程里与app一起打包。在初始化AFHTTPSessionManager时设置securityPolicy的pinnedCertificates属性即可。下面我们来看看自签名证书的核心验证逻辑代码。在AFUrlSessionManager的m文件里,可以看到有个NSURLSessionDelegate的代理方法:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {//表示验证服务端证书
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

这个就是NSURLSession在发https请求时遇到要求证书验证时的回调。具体的逻辑还是在[self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host],核心代码如下:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    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) {//如果是系统验证,采用系统验证的结果
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }//系统验证不通过,但是有设置不允许无效证书,整个验证结果就是false了

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone://if (self.SSLPinningMode == AFSSLPinningModeNone)已处理,实际到不了该case
        default:
            return NO;
        case AFSSLPinningModeCertificate: {//如果验证证书
            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);//设置serverTrust的可信任锚点证书,也就是我们提供的证书

            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;
                }
            }//查看serverTrust的证书链是否有一个在我们提供的证书里列表里(与app一起打包的证书)
            
            return NO;
        }
        case AFSSLPinningModePublicKey: {//验证公钥,不验证整个证书
            NSUInteger trustedPublicKeyCount = 0;
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

            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;
}

上面的代码注释解释的很清楚了。需要注意的是有两点:

  1. 对于自签名的https证书,需要自己验证。
  2. 打包到App里的自签名证书会存在过期问题,需要在到期之前提前处理。

最后说一句,我们的存储服务器就是https证书就是权威CA签发的,意味着啥都不用做Orz...

参考链接

相关文章

  • iOS https认证

    iOS https认证 项目背景 最近在做iOS 热更新,出于公司信息安全限制没使用JSPatch平台来下发js,...

  • iOS HTTPS 双向认证

    iOS HTTPS 双向认证 @(iOS)[网络,HTTPS] 搞了半天,记录一下,坑很多。双向认证,就是在访问网...

  • iOS https 双向认证

    /** 一.非浏览器应用(iOS app)与服务器AFNetworking HTTPS ssl认证 虽然是HTTP...

  • iOS HTTPS双向认证

    一般情况下对于HTTPS,如果公司申请认证之后,客户端是不需要做什么操作。但是如果是自建证书,没有通过CA认证的话...

  • iOS_HTTPS认证

    HTTPS认证 单向认证:指客户端验证服务端的身份,服务端不验证客户端身份双向认证:指客户端验证服务端身份,服务端...

  • iOS中的HTTPS认证

    一、HTTPS认证 1. 会话认证机制 iOS 中会话认证机制共有四种,大体分为两种类型: 单向认证双向认证; N...

  • Https双向认证-合集

    iOS实用篇:Https双向认证[https://www.jianshu.com/p/72bf60b5f94d]i...

  • iOS中的安全与加密

    iOS中的安全与加密 一。HTTPS双向认证 Charles是大家所熟悉的抓包工具,如果网络请求未经过双向认证,那...

  • iOS实用篇:Https双向认证

    年前的时候,关于苹果要强制https的传言四起,虽然结果只是一个“谣言”,但是很明显的这是迟早会到来的,间接上加速...

  • HTTPS认证

    目录:1、https认证原理简介2、单向认证、双向认证 1、https认证原理简介 (转自lanp74的博客) H...

网友评论

      本文标题:iOS https认证

      本文链接:https://www.haomeiwen.com/subject/audduqtx.html