iOS基础框架深究之AFNetWorking day 2

本文详细介绍了HTTP与HTTPS的基础知识,包括HTTP的无状态性、请求方法、HTTPS的安全解决方案等。接着讲解了HTTPS的加密机制、证书验证以及AFSecurityPolicy在iOS开发中的作用,帮助开发者理解如何确保网络连接的安全。

昨天过了一遍网络监听的类,整的脑袋瓜子嗡嗡的,哈哈

在我们平时的开发中,对网络连接安全方面所做的努力,应该占据很重要的位置。

在解释今天AFSecurityPolicy之前,我们先把基础的http/https知识简单的普及一下。

preview

 

preview

preview

preview

preview

preview

preview

preview

 

preview

preview

具体内容你们移步精读《图解HTTP》 - 知乎 去查看吧。

 HTTP:

1.HTTp协议用于客户端和服务器之间的通信

2.通过请求和相应的交换达成通信

客户端请求:

 服务端响应:

3.HTTP是不保存状态的协议

HTTP本身不会对请求和相应之间的通信状态进行保存。什么意思呢?就是说,当有新的请求到来的时候,HTTP就会产生新的响应,对之前的请求和响应的信息不做任何存储。这也是为了快速的处理事务,保持良好的可伸展性而特意设置成这样的。

4.请求URI定位资源

URI算是一个位置的索引,这样就能很方便的访问到互联网上的各种资源。

5.告知服务器意图的HTTP方法

①GET: 直接访问URI识别的资源,也就是说根据URI来获取资源。URI和URL的区别比较与理解_小猛同学的博客-CSDN博客_uri 这里讲了URI和URL的区别。

②POST: 用来传输实体的主体。

③PUT: 用来传输文件。

④HEAD: 用来获取报文首部,和GET方法差不多,只是响应部分不会返回主体内容。

⑤DELETE: 删除文件,和PUT恰恰相反。按照请求的URI来删除指定位置的资源。

⑥OPTIONS: 询问支持的方法,用来查询针对请求URI指定的资源支持的方法。

⑦TRACE: 追踪路径,返回服务器端之前的请求通信环信息。

⑧CONNECT: 要求用隧道协议连接代理,要求在与代理服务器通信时简历隧道,实现用隧道协议进行TCP通信。SSL(Secure Sockets Layer)和TLS(Transport Layer Security)就是把通信内容加密后进行隧道传输的。

6.管线化让服务器具备了相应多个请求的能力

7.Cookie让HTTP有迹可循

HTTP是一套很简单的通信协议,因此也非常的高效。但是由于通信数据都是明文发送的,很容易被拦截后造成破坏。在互联网越来越发达的时代,对通信数据的安全要求也越来越高。

HTTPS

HTTPS是一个通信安全的解决方案,可以说相对已经非常安全。为什么他会是一个很安全的协议呢?下面会做出解释

大家可以看这边文章,解释的很有意思。简单粗暴系列之HTTPS原理 - 简书

HTTP+加密+认证+完整性保护 = HTTPS

其实HTTPS是身披SSL外壳的HTTP,这句话怎么理解呢?

大家应该都知道HTTP是应用层协议,但是HTTPS并非是应用层的一种新协议,知识HTTP通信接口部分用SSL或TLS协议代替而已。

通常HTTP直接和TCP通信,当使用SSL时就不同了。要先和SSL通信,再由SSL和TCP通信。

这里再说一些关于加密的题外话:

现如今,通常加密和解密的算法都是公开的。举个例子:a*b = 200,假如a是你知道的密码,b是需要被加密的数据。那么这个*号就是一个很简单的加密算法。这个算法是如此简单。但是如果想要在不知道a和b其中一个的情况下进行破解也是很困难的。就算我们知道了200然后得到ab这个也很难。假设知道了密码a那么b就很容易算出b = 200/a。

实际中的加密算法比这个要负责的多。

介绍两种常用的加密方法:

1.共享密钥加密

2.公开密钥加密

共享密钥加密就是加密和解密通用一个密钥,也称为对称加密。优点是加密解密速度快,缺点是一旦密钥泄露,别人也能解密数据。

公开密钥加密恰恰能解决共享密钥加密的困难,过程是这样的:

①发文方使用对方的公开密钥进行加密 

②接受方在使用自己的私有密钥进行解密

关于公开密钥,也就是非对称加密 可以看看这篇文章RSA算法原理(二) - 阮一峰的网络日志

原理都是一样的,这个不同于刚才举得a和b的例子,就算知道了结果和公钥,破解出被加密的数据是非常难的。这里边主要涉及到了复杂的数学理论。

HTTPS采用混合加密机制

HTTPS采用共享密钥加密和公开密钥加密两者并用的混合加密机制。

好了我们大概已经知道了HTTPS是如何加密的了,那么这个相互认证的过程是怎么样的呢?

在网上看到了这篇博客,  AFNetwork 3.0 源码解读(五)AFSecurityPolicy_FY_Fish的专栏-CSDN博客 把他描述的剪切下来了

==================================================================

注意黄色的部分,这个指明了,我们平时使用的一个场景。这篇文章会很长,不仅仅是为了解释HTTPS,还为了能够增加记忆,当日后想看看的时候,就能通过读这边文章想起大部分的HTTPS的知识。下边解释一些更加详细的HTTPS过程。

 

 

好了HTTPS就说到这里了,AFSecurityPolicy这个类其实就是为了验证证书是否正确 

 还是先看透文件里边有什么东西。要实现认证功能需要添加系统的Security,这个是必须的。

下面的这个枚举的意思是:

1. AFSSLPinningModeNone 代表无条件信任服务器证书

2. AFSSLPinningModePublicKey 代表会对服务器返回的证书中的PublicKey进行验证,通过则通过,否则不通过。

3. AFSSLPinningModeCertificate 代表会对服务器返回的证书同本地证书进行校验,通过则通过,否则不通过

 说的是AFSecurityPolicy用来评价通过X.509(数字证书的标准)的数字正式和公开密钥进行的安全网络连接是否值得信任。在应用内添加SSL证书能够有效的防止中间人的攻击和安全漏洞。强烈建议涉及到用户敏感或者隐私数据或金融信息的应用全部网络连接都采用使用SSL的HTTPS连接。

 返回SSL Pinning的类型。默认的是AFSSLPinningModeNone。

 

 这个属性保存着所以的可用做校验的证书的集合。AFNetworking默认会搜索工程中所以.cer的证书文件。如果想制定某些证书,可使用certificatesInBundle在目标路径下加载证书,然后调用policyWithPinningMode:withPinnedCertificates创建一个本类对象。

注意:只要在证书集合中任何一个校验通过,evaluateServerTrust:forDomain:就会返回true,即通过校验。

使用允许无效或者过期的证书,默认是不允许。

 

是否验证证书中的域名domain 

 返回指定bundle中的证书,如果使用AFNetworking的证书验证,就必须实现此方法,并且使用

policyWithPinningMode:withPinnedCertificates方法来创建实例对象。

 

默认的实例对象,默认的认证设置为:

1.不允许无效或过期的证书

2.验证domain名称

3.不对证书和公钥进行验证

 这两个方法是创建security policy的方法

 核心方法:使用起来就是这样的,这个方法AFNetworking在北部调用了。这个后边会说到

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

AFSecurityPolicy *securityPolicy =[ [AFsecurityPolicy alloc] init] ;

[securityPolicy setAllowInvalidCertificates:NO];

[securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate];

[securityPolicy setValidatesDomainName:YES];

[securityPolicy setValidatesCertificateChain:NO];

manager.securityPolicy = securityPolicy;

好了本类的头文件己经看完了,接下来看.m

我们先看这个函数

// 在证书中获取公钥
static id AFPublicKeyForCertificate(NSData *certificate) {
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecCertificateRef allowedCertificates[1];
    CFArrayRef tempCertificates = nil;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;

    // 1. 根据二进制的certificate生成SecCertificateRef类型的证书
    // NSData *certificate 通过CoreFoundation (__bridge CFDataRef)转换成 CFDataRef
    // 看下边的这个方法就可以知道需要传递参数的类型
    /*
     SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator,
     CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
     */
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);

    // 2.如果allowedCertificate为空,则执行标记_out后边的代码
    __Require_Quiet(allowedCertificate != NULL, _out);

    // 3.给allowedCertificates赋值
    allowedCertificates[0] = allowedCertificate;

    // 4.新建CFArra: tempCertificates
    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);

    // 5. 新建policy为X.509
    policy = SecPolicyCreateBasicX509();

    // 6.创建SecTrustRef对象,如果出错就跳到_out标记处
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
    // 7.校验证书的过程,这个不是异步的。
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);

    // 8.在SecTrustRef对象中取出公钥
    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

_out:
    if (allowedTrust) {
        CFRelease(allowedTrust);
    }

    if (policy) {
        CFRelease(policy);
    }

    if (tempCertificates) {
        CFRelease(tempCertificates);
    }

    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }

    return allowedPublicKey;
}

在二进制的文件中获取公钥的过程是这样

1.NSData *certificate ->CFDataRef->(SecCertificateCreateWithData)->SecCertificateRef allowedCertificate

2.判断SecCertificate allowedCertificate是不是空,如果为空,直接跳转到后边的代码

3.allowedCertificate保存到allowedCertificates数组中

4.allowedCertificates ->(CFArrayCreat)->SecCertificateRef allowedCertificates[1]

5.根据函数SecPolicyCreateBasicX509()->SecPolicyRef policy

6.SecTrustCreateWithCertificates(tempCertificates,policy,&allowedTrust)->生成SecTrustRef allowedTrust

7.SecTrustEvaluate(allowedTrust,&result)校验证书

8.(__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust)->得到公钥id allowedTrust

这个过程我们平时不怎么用,了解下就行,真需要的时候知道去哪里找资料就行了。

这里边值得学习的地方是:

__Require_Quiet和__Require_noErr_Quiet这两个宏定义。

我们看看他们内部是怎么定义的

 可以看出这个宏的用途是:当条件返回false时,执行标记以后的代码

可以看出这个宏的用途是:当条件抛出异常时,执行标记以后的代码

这样就有很多使用场景了。当必须要对条件进行判断的时候,我们有下边几种方案了

1. #ifdef 这个事编译特性

2. if else 发麻层次的判断

3 __Require_XXX 宏 

 

_out就是一个标记,这段代码__Require_Quiet 到_out之间的代码不会执行

再来看看下边的方法,主要是把key导出为NSData

这个方法是比较两个key是否相等,如果是iOS/watch/tv直接使用idEqual方法就可以比较。应为SecKeyRef本质上是一个struct,是不能直接用isEqual比较的,正好使用上边的那个方法把它转为NSData就可以了。 

来看原文中这段解释

 大概意思是分两种方式:下边的自定义的意思是,用户是否是自己主动设置信任的,比如有些弹窗,用户点击了信任

1.用户自定义的,成功是kSecTrustResultProceed 失败是kSecTrustResultDeny

2. 非用户定义的,成功是kSecTrustResultUnspecified 失败是kSecTrustResultRecoverableTrustFailure

这就不难解释上边最后的那个或判断了。

 

 

 

 

 

 

 

- (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);

    // AFSSLPinningModeNone 不校验证书,
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }


    // 代码能够走到这里说明两点
    // 1.通过了根证书的验证
    // 2.allowInvalidCertificates = YES

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        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);

            // 校验能够信任
            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);

            // 找到相同的公钥就通过
            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;
}

#pragma mark - NSKeyValueObserving

+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
    return [NSSet setWithObject:@"pinnedCertificates"];
}

#pragma mark - NSSecureCoding

+ (BOOL)supportsSecureCoding {
    return YES;
}

- (instancetype)initWithCoder:(NSCoder *)decoder {

    self = [self init];
    if (!self) {
        return nil;
    }

    self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
    self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
    self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
    self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
    [coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
    [coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
    [coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
}

#pragma mark - NSCopying

- (instancetype)copyWithZone:(NSZone *)zone {
    AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
    securityPolicy.SSLPinningMode = self.SSLPinningMode;
    securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
    securityPolicy.validatesDomainName = self.validatesDomainName;
    securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];

    return securityPolicy;
}

好了,这篇就到这了,大体了解了SSL校验是怎么一回事了,而且知道了该如何操作。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青年没有路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值