在网络请求中,我们经常使用http协议,http全称也就是超文本传输协议,通常使用版本是1.1版本,为了安全我们会使用https。这两个的区别就在于https是http经过加密的http,所以https更安全。正因为https有了加密这个过程,也使得https的响应速度没有http快。
http
http的历程
距今,http共有四个版本,分别是1991年的0.9版本的推出,这个版本仅支持GET命令;1996年推出了1.0版本,这个版本支持GET、POST、HEAD,增加了请求头,但是这个版本每次TCP连接只支持一次请求;半年后1997年就推出了我们现在使用最广泛的1.1版本,支持持久连接,还提出了管道机制;虽然1.1版本支持客户端同时发送多个请求,但是服务器的响应还是依次执行,所以后来推了2.0版本,这个版本就解决了服务器可以同时响应多个请求,还压缩了请求头,服务器还能自动推送静态资源
持久连接:就是指client和server之间保持持久连接,只有在双方长时间无响应的状态下才会断开连接。减少TCP连接的重复建立和断开所造成的额外开销
管道机制:同一个TCP连接可以发多个请求
http其实是无状态的,也就是说client和server之间不做状态保存,就减少了CPU和内存的消耗。但是这样就不能维持一个双向的会话,所以就用cookie来保存这个状态,比如我们开发中请求头中会有一个seeeionId什么的。
http的缺点:
1、明文传输,内容容易被窃听
2、没有验证通信方的身份,容易遭遇伪装
3、无法验证报文的完整性,可能遭遇篡改
因为如此,所以我们要对http进行加密。加密又分为通信加密和内容加密。内容加密容易被篡改,而通信加密通过ssl建立安全套接层,建立一个安全的套接线路,所以就有了https。
https
https都不能说是一种协议,他只是http之上做了一层ssl加密
ssl加密不仅提供了加密处理,还提供了证书,确保了通信线路的安全
https = http + 加密 + 认证 + 完整性保护
证书有CA证书和自签证书。CA证书是第三方机构给我们颁发的可信的证书
加密
1、共享密钥加密:加密和解密公用同一个密钥,又叫对称加密,比较快
2、公开私钥加密:有公开密钥和私有密钥,较慢
https上面两种加密都用了,在交换密钥使用公开密钥加密,在通信交换报文使用共享秘钥加密
https的缺点:通过ssl加密,处理速度会变慢,消耗CPU和内存资源,通信时间会延长。
AFSecurityPolicy
AFSecurityPolicy的作用就是客户端的证书验证,对于CA证书的验证我们不需要做什么,除非是自签证书,需要把证书放到本地。
如果是自签证书我们要做些什么呢?如下实例代码
- (void)test{
NSString *urlstr = @"http://www.12306.cn/mormhweb/";
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 默认这个属性不用管,默认就是CA证书的认证,除非是银行等相关对安全要求比较高的应用,才会走自签证书的认证,才会去调用下面的方法
manager.securityPolicy = [self securityPolicy];
[manager GET:urlstr parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
}
- (AFSecurityPolicy *)securityPolicy{
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"srca" ofType:@"cer"];
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:cerData];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
return securityPolicy;
}
不管是CA证书还是自签证书的认证,AFN都已经为我们封装好,具体怎么实现,我们一步一步看源码
AFSSLPinningMode都有哪值呢?
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
//不使用固定证书(本地)验证服务器。直接从客户端系统中的受信任颁发机构 CA 列表中去验证,默认
AFSSLPinningModeNone,
// 代表会对服务器返回的证书中的PublicKey进行验证,通过则通过,否则不通过
AFSSLPinningModePublicKey,
// 代表会对服务器返回的证书同本地证书全部进行校验,通过则通过,否则不通过
AFSSLPinningModeCertificate,
};
什么时候需要证书认证呢?就是在NSURLSessionDataDelegate的回调方法中
//收到服务端的challenge,例如https需要验证证书等 ats开启
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//挑战处理类型为 默认
/*
NSURLSessionAuthChallengeUseCredential:使用指定的证书
NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
NSURLSessionAuthChallengeRejectProtectionSpace:拒绝此挑战,并尝试下一个验证保护空间;忽略证书参数
*/
//挑战处理类型为默认
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;//证书
// 自定义方法,用来如何应对服务器端的认证挑战
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 1.判断接收服务器挑战的方法是否是信任证书
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//只需要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 2.信任评估通过,就从受保护空间里面拿出证书,回调给服务器,告诉服务,我信任你,你给我发送数据吧.
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 确定挑战的方式
if (credential) {
//证书挑战
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
//默认挑战
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//取消挑战
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
//默认挑战方式
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
//完成挑战
// 3.将信任凭证发送给服务端
if (completionHandler) {
completionHandler(disposition, credential);
}
}
其中校验最关键的方法就是AFSecurityPolicy提供的这个方法
-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain;
这个方法做了什么呢?我们看源码
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//如果有服务器域名、设置了允许信任无效或者过期证书(自签名证书)、需要验证域名、没有提供证书或者不验证证书,返回no。后两者和allowInvalidCertificates为真的设置矛盾,说明这次验证是不安全的。
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."
//如果想要实现自签名的HTTPS访问成功,必须设置pinnedCertificates,且不能使用defaultPolicy
NSLog(@"In order to val idate a domain name for self signed certificates, you MUST use pinning.");
//不受信任,返回
return NO;
}
//用来装验证策略
NSMutableArray *policies = [NSMutableArray array];
//生成验证策略。如果要验证域名,就以域名为参数创建一个策略,否则创建默认的basicX509策略
if (self.validatesDomainName) {
// 如果需要验证domain,那么就使用SecPolicyCreateSSL函数创建验证策略,其中第一个参数为true表示为服务器证书验证创建一个策略,第二个参数传入domain,匹配主机名和证书上的主机名
//1.__bridge:CF和OC对象转化时只涉及对象类型不涉及对象所有权的转化
//2.__bridge_transfer:常用在讲CF对象转换成OC对象时,将CF对象的所有权交给OC对象,此时ARC就能自动管理该内存
//3.__bridge_retained:(与__bridge_transfer相反)常用在将OC对象转换成CF对象时,将OC对象的所有权交给CF对象来管理
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 如果不需要验证domain,就使用默认的BasicX509验证策略
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 为serverTrust设置验证策略,用策略对serverTrust进行评估
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
//如果是AFSSLPinningModeNone(不做本地证书验证,从客户端系统中的受信任颁发机构 CA 列表中去验证服务端返回的证书)
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//不使用ssl pinning 但允许自建证书,直接返回YES;否则进行第二个条件判断,去客户端系统根证书里找是否有匹配的证书,验证serverTrust是否可信,直接返回YES
//why需要allowInvalidCertificates?
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//如果验证无效AFServerTrustIsValid,而且allowInvalidCertificates不允许自签,返回NO
return NO;
}
switch (self.SSLPinningMode) {
//上一部分已经判断过了,如果执行到这里的话就返回NO
case AFSSLPinningModeNone:
default:
return NO;
//验证证书类型
// 这个模式表示用证书绑定(SSL Pinning)方式验证证书,需要客户端保存有服务端的证书拷贝
// 注意客户端保存的证书存放在self.pinnedCertificates中
case AFSSLPinningModeCertificate: {
// 全部校验(nsbundle .cer)
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//把证书data,用系统api转成 SecCertificateRef 类型的数据,SecCertificateCreateWithData函数对原先的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书
for (NSData *certificateData in self.pinnedCertificates) {
//cf arc brige:cf对象和oc对象转化 __bridge_transfer:把cf对象转化成oc对象
//brige retain:oc转成cf对象
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 将pinnedCertificates设置成需要参与验证的Anchor Certificate(锚点证书,通过SecTrustSetAnchorCertificates设置了参与校验锚点证书之后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书),具体就是调用SecTrustEvaluate来验证
//serverTrust是服务器来的验证,有需要被验证的证书
// 把本地证书设置为根证书,
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);
//reverseObjectEnumerator逆序
// 倒序遍历
//这里的证书链顺序是从叶节点到根节点
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
//如果我们的证书中,有一个和它证书链中的证书匹配的,就返回YES
// 是否本地包含相同的data
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
//没有匹配的
return NO;
}
//公钥验证 AFSSLPinningModePublicKey模式同样是用证书绑定(SSL Pinning)方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
// 从serverTrust中取出服务器端传过来的所有可用的证书,并依次得到相应的公钥
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
//遍历服务端公钥
for (id trustChainPublicKey in publicKeys) {
//遍历本地公钥
for (id pinnedPublicKey in self.pinnedPublicKeys) {
//判断如果相同 trustedPublicKeyCount+1
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}