HTTPS是基于HTTP和SSL的合成,在HTTP的基礎(chǔ)上多了一步證書認(rèn)證的過程,其大致步驟如下:
1.客戶端向服務(wù)端發(fā)送請求.
2.服務(wù)端返回證書包括公鑰和一些基本信息.服務(wù)端配置證書可向第三方申請證書,此時(shí)不會彈出警告框,也可以自己創(chuàng)建證書,訪問時(shí)會彈出警告框.
3.客戶端驗(yàn)證證書,如果證書不合法,彈出HTTPS警告,合法的話,生成一個(gè)隨機(jī)數(shù),將隨機(jī)數(shù)利用公鑰加密,作為對稱密鑰,傳給服務(wù)器.
4.服務(wù)器利用公鑰解析,得到隨機(jī)數(shù),最為對稱加密的密鑰,將要返回的數(shù)據(jù)進(jìn)行加密傳輸給客戶端.
5.客戶端利用隨機(jī)數(shù)對稱密鑰解密數(shù)據(jù).
這只是單向認(rèn)證,還有雙向認(rèn)證.
下面看看AFN中具體實(shí)現(xiàn):
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{ // 默認(rèn)處理
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// 自定義驗(yàn)證服務(wù)端挑戰(zhàn)
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 默認(rèn)處理服務(wù)端挑戰(zhàn)(單向認(rèn)證)
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 根據(jù)安全策略評估服務(wù)端的信任
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 創(chuàng)建信任
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
// 信任挑戰(zhàn)
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
// 默認(rèn)處理挑戰(zhàn)
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
// 取消挑戰(zhàn)
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
// 默認(rèn)處理挑戰(zhàn)
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
// 根據(jù)前面的處理和信任,完成挑戰(zhàn)
if (completionHandler) {
completionHandler(disposition, credential);
}
}
這里主要做了以下幾件事情:
1.如果實(shí)現(xiàn)了自定義的處理挑戰(zhàn),就用自定的處理方式.
2.如果沒有實(shí)現(xiàn)自定義的處理挑戰(zhàn)方式,就用系統(tǒng)的處理方式.
根據(jù)安全策略評估服務(wù)端的信任的代碼如下:
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{ // 如果有域名,且允許自建證書,需要驗(yàn)證域名, AFSSLPinningModeNone或證書個(gè)數(shù)為0時(shí)
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
// 需要驗(yàn)證域名
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) {
// 不需要驗(yàn)證
case AFSSLPinningModeNone:
default:
return NO;
// 需要驗(yàn)證證書
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;
}
// 需要驗(yàn)證公鑰
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請求的話,如果是付費(fèi)證書,我們什么也不用做,如果是自建證書需要在plist文件設(shè)置可以返回不安全的請求,然后AFN已經(jīng)將要做的事情幫我們做了,我們只需寫:
// 允許自簽名證書
policy.allowInvalidCertificates = YES;
// 可以驗(yàn)證域名(需要導(dǎo)入自簽名證書)
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在系統(tǒng)驗(yàn)證之前,幫我們驗(yàn)證了,如果驗(yàn)證通過了走系統(tǒng)的驗(yàn)證,系統(tǒng)驗(yàn)證如果匹配到了證書,就返回安全的鏈接,如果匹配不成功,判斷ATS,ATS關(guān)閉,返回不安全的鏈接,如果ATS開啟,拒絕這個(gè)請求.