HTTPS是基于HTTP和SSL的合成,在HTTP的基础上多了一步证书认证的过程,其大致步骤如下:
1.客户端向服务端发送请求.
2.服务端返回证书包括公钥和一些基本信息.服务端配置证书可向第三方申请证书,此时不会弹出警告框,也可以自己创建证书,访问时会弹出警告框.
3.客户端验证证书,如果证书不合法,弹出HTTPS警告,合法的话,生成一个随机数,将随机数利用公钥加密,作为对称密钥,传给服务器.
4.服务器利用公钥解析,得到随机数,最为对称加密的密钥,将要返回的数据进行加密传输给客户端.
5.客户端利用随机数对称密钥解密数据.
这只是单向认证,还有双向认证.
下面看看AFN中具体实现:
- (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);
}
}
这里主要做了以下几件事情:
1.如果实现了自定义的处理挑战,就用自定的处理方式.
2.如果没有实现自定义的处理挑战方式,就用系统的处理方式.
根据安全策略评估服务端的信任的代码如下:
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{ // 如果有域名,且允许自建证书,需要验证域名, AFSSLPinningModeNone或证书个数为0时
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
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;
}
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;
}
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;
}
对于我们自己做做https请求的话,如果是付费证书,我们什么也不用做,如果是自建证书需要在plist文件设置可以返回不安全的请求,然后AFN已经将要做的事情帮我们做了,我们只需写:
// 允许自签名证书
policy.allowInvalidCertificates = YES;
// 可以验证域名(需要导入自签名证书)
policy.validatesDomainName = YES;
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
policy.pinnedCertificates = certificates;
AFN在系统验证之前,帮我们验证了,如果验证通过了走系统的验证,系统验证如果匹配到了证书,就返回安全的链接,如果匹配不成功,判断ATS,ATS关闭,返回不安全的链接,如果ATS开启,拒绝这个请求.