在我們平時的開發中,對網絡連接安全方面所做的努力,應該占據很重要的位置。
在解釋AFSecurityPolicy之前,我們先把基礎的http/https 知識簡單的普及一下。獲取這方面的信息可通過這本書:圖解HTTP
HTTP:
1.HTTP協議用於客戶端和服務器端之間的通信
2.通過請求和相應的交換達成通信
客戶端請求:
服務器端響應:
3.HTTP是不保存狀態的協議
HTTP自身不會對請求和相應之間的通信狀態進行保存。什么意思呢?就是說,當有新的請求到來的時候,HTTP就會產生新的響應,對之前的請求和響應的保溫信息不做任何存儲。這也是為了快速的處理事務,保持良好的可伸展性而特意設計成這樣的。
4.請求URI定位資源
URI算是一個位置的索引,這樣就能很方便的訪問到互聯網上的各種資源。
5.告知服務器意圖的HTTP方法
①GET: 直接訪問URI識別的資源,也就是說根據URI來獲取資源。
②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是一個通信安全的解決方案,可以說相對已經非常安全。為什么它會是一個很安全的協議呢?下邊會做出解釋
大家可以看看這篇文章,解釋的很有意思 。
HTTP + 加密 + 認證 + 完整性保護 = HTTPS
其實HTTPS是身披SSL外殼的HTTP,這句話怎么理解呢?
大家應該都知道HTTP是應用層的協議,但HTTPS並非是應用層的一種新協議,只是HTTP通信接口部分用SSL或TLS協議代替而已。
通常 HTTP 直接和TCP通信,當使用SSL時就不同了。要先和SSL通信,再由SSL和TCP通信。
這里再說一些關於加密的題外話:
現如今,通常加密和解密的算法都是公開的。舉個例子: a * b = 200,加入a是你知道的密碼,b是需要被加密的數據,200 是加密后的結果。那么這里這個*號就是一個很簡單的加密算法。這個算法是如此簡單。但是如果想要在不知道a和b其中一個的情況下進行破解也是很困難的。就算我們知道了200 然后得到a b 這個也很難。假設知道了密碼a 那么b就很容易算出b = 200 / a 。
實際中的加密算法比這個要復雜的多。
介紹兩種常用加密方法:
1.共享密鑰加密
2.公開密鑰加密
共享密鑰加密就是加密和解密通用一個密鑰,也稱為對稱加密。優點是加密解密速度快,缺點是一旦密鑰泄露,別人也能解密數據。
公開密鑰加密恰恰能解決共享密鑰加密的困難,過程是這樣的:
①發文方使用對方的公開密鑰進行加密
②接受方在使用自己的私有密鑰進行解密
關於公開密鑰,也就是非對稱加密 可以看看這篇文章 RSA算法原理
原理都是一樣的,這個不同於剛才舉得a和b的例子,就算知道了結果和公鑰,破解出被機密的數據是非常難的。這里邊主要涉及到了復雜的數學理論。
HTTPS采用混合加密機制
HTTPS采用共享密鑰加密和公開密鑰加密兩者並用的混合加密機制。
好了我們大概已經知道了HTTPS是如何加密的了,那么這個相互認證過程是怎么樣的呢 ?
在網上看到了這篇博客,http://blog.csdn.net/yuwuchaio/article/details/50469183 把他描述的剪切下來了
==================================================================
==================================================================
注意黃色的部分,這個指明了,我們平時使用的一個場景。這篇文章會很長,不僅僅是為了解釋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在內部調用了。這個后邊會說到
1 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; 2 3 AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init]; 4 [securityPolicy setAllowInvalidCertificates:NO]; 5 [securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate]; 6 [securityPolicy setValidatesDomainName:YES]; 7 [securityPolicy setValidatesCertificateChain:NO]; 8 9 manager.securityPolicy = securityPolicy;
好了本類的頭文件已經看完了,接下來看.m
我們先看這個函數
1 // 在證書中獲取公鑰 2 static id AFPublicKeyForCertificate(NSData *certificate) { 3 id allowedPublicKey = nil; 4 SecCertificateRef allowedCertificate; 5 SecCertificateRef allowedCertificates[1]; 6 CFArrayRef tempCertificates = nil; 7 SecPolicyRef policy = nil; 8 SecTrustRef allowedTrust = nil; 9 SecTrustResultType result; 10 11 // 1. 根據二進制的certificate生成SecCertificateRef類型的證書 12 // NSData *certificate 通過CoreFoundation (__bridge CFDataRef)轉換成 CFDataRef 13 // 看下邊的這個方法就可以知道需要傳遞參數的類型 14 /* 15 SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator, 16 CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0); 17 */ 18 allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); 19 20 // 2.如果allowedCertificate為空,則執行標記_out后邊的代碼 21 __Require_Quiet(allowedCertificate != NULL, _out); 22 23 // 3.給allowedCertificates賦值 24 allowedCertificates[0] = allowedCertificate; 25 26 // 4.新建CFArra: tempCertificates 27 tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); 28 29 // 5. 新建policy為X.509 30 policy = SecPolicyCreateBasicX509(); 31 32 // 6.創建SecTrustRef對象,如果出錯就跳到_out標記處 33 __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out); 34 // 7.校驗證書的過程,這個不是異步的。 35 __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out); 36 37 // 8.在SecTrustRef對象中取出公鑰 38 allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust); 39 40 _out: 41 if (allowedTrust) { 42 CFRelease(allowedTrust); 43 } 44 45 if (policy) { 46 CFRelease(policy); 47 } 48 49 if (tempCertificates) { 50 CFRelease(tempCertificates); 51 } 52 53 if (allowedCertificate) { 54 CFRelease(allowedCertificate); 55 } 56 57 return allowedPublicKey; 58 }
在二進制的文件中獲取公鑰的過程是這樣
① NSData *certificate -> CFDataRef -> (SecCertificateCreateWithData) -> SecCertificateRef allowedCertificate
②判斷SecCertificateRef allowedCertificate 是不是空,如果為空,直接跳轉到后邊的代碼
③allowedCertificate 保存在allowedCertificates數組中
④allowedCertificates -> (CFArrayCreate) -> SecCertificateRef allowedCertificates[1]
⑤根據函數SecPolicyCreateBasicX509() -> SecPolicyRef policy
⑥SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust) -> 生成SecTrustRef allowedTrust
⑦SecTrustEvaluate(allowedTrust, &result) 校驗證書
⑧(__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust) -> 得到公鑰id allowedPublicKey
這個過程我們平時也不怎么用,了解下就行了,真需要的時候知道去哪里找資料就行了。
這里邊值得學習的地方是:
__Require_Quiet 和 __Require_noErr_Quiet 這兩個宏定義。
我們看看他們內部是怎么定義的
可以看出這個宏的用途是:當條件返回false時,執行標記以后的代碼
可以看出這個宏的用途是:當條件拋出異常時,執行標記以后的代碼
這樣就有很多使用場景了。當必須要對條件進行判斷的時候,我們有下邊幾種方案了
1.#ifdef 這個是編譯特性
2. if else 代碼層次的判斷
3 __Require_XXX 宏
_out 就是一個標記,這段代碼__Require_Quiet 到_out之間的代碼不會執行
再來看看下邊的方法,主要是把key到出為NSData
這個方法是比較兩個key是否相等,如果是ios/watch/tv直接使用isEqual方法就可進行比較。應為SecKeyRef本質上是一個struct,是不能直接用isEqual比較的,正好使用上邊的那個方法把它轉為NSData就可以了。
來看原文中這段解釋
大概意思是分兩種方式:下邊的自定義的意思是,用戶是否是自己主動設置信任的,比如有些彈窗,用戶點擊了信任
1.用戶自定義的,成功是 kSecTrustResultProceed 失敗是kSecTrustResultDeny
2.非用戶定義的, 成功是kSecTrustResultUnspecified 失敗是kSecTrustResultRecoverableTrustFailure
這就不難解釋上邊最后的那個或判斷了。
1 - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust 2 forDomain:(NSString *)domain 3 { 4 if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) { 5 // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html 6 // According to the docs, you should only trust your provided certs for evaluation. 7 // Pinned certificates are added to the trust. Without pinned certificates, 8 // there is nothing to evaluate against. 9 // 10 // From Apple Docs: 11 // "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors). 12 // Instead, add your own (self-signed) CA certificate to the list of trusted anchors." 13 NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning."); 14 return NO; 15 } 16 17 NSMutableArray *policies = [NSMutableArray array]; 18 if (self.validatesDomainName) { 19 [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; 20 } else { 21 [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()]; 22 } 23 24 SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); 25 26 // AFSSLPinningModeNone 不校驗證書, 27 if (self.SSLPinningMode == AFSSLPinningModeNone) { 28 return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); 29 } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) { 30 return NO; 31 } 32 33 34 // 代碼能夠走到這里說明兩點 35 // 1.通過了根證書的驗證 36 // 2.allowInvalidCertificates = YES 37 38 switch (self.SSLPinningMode) { 39 case AFSSLPinningModeNone: 40 default: 41 return NO; 42 case AFSSLPinningModeCertificate: { // 全部校驗 43 NSMutableArray *pinnedCertificates = [NSMutableArray array]; 44 for (NSData *certificateData in self.pinnedCertificates) { 45 [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; 46 } 47 48 // 把本地的證書設為根證書,即服務器應該信任的證書 49 SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); 50 51 // 校驗能夠信任 52 if (!AFServerTrustIsValid(serverTrust)) { 53 return NO; 54 } 55 56 // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA) 57 NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); 58 59 // 判斷本地證書和服務器證書是否相同 60 for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { 61 if ([self.pinnedCertificates containsObject:trustChainCertificate]) { 62 return YES; 63 } 64 } 65 66 return NO; 67 } 68 case AFSSLPinningModePublicKey: { 69 NSUInteger trustedPublicKeyCount = 0; 70 NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); 71 72 // 找到相同的公鑰就通過 73 for (id trustChainPublicKey in publicKeys) { 74 for (id pinnedPublicKey in self.pinnedPublicKeys) { 75 if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) { 76 trustedPublicKeyCount += 1; 77 } 78 } 79 } 80 return trustedPublicKeyCount > 0; 81 } 82 } 83 84 return NO; 85 }
1 #pragma mark - NSKeyValueObserving 2 3 + (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys { 4 return [NSSet setWithObject:@"pinnedCertificates"]; 5 } 6 7 #pragma mark - NSSecureCoding 8 9 + (BOOL)supportsSecureCoding { 10 return YES; 11 } 12 13 - (instancetype)initWithCoder:(NSCoder *)decoder { 14 15 self = [self init]; 16 if (!self) { 17 return nil; 18 } 19 20 self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue]; 21 self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))]; 22 self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))]; 23 self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))]; 24 25 return self; 26 } 27 28 - (void)encodeWithCoder:(NSCoder *)coder { 29 [coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))]; 30 [coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))]; 31 [coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))]; 32 [coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))]; 33 } 34 35 #pragma mark - NSCopying 36 37 - (instancetype)copyWithZone:(NSZone *)zone { 38 AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init]; 39 securityPolicy.SSLPinningMode = self.SSLPinningMode; 40 securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates; 41 securityPolicy.validatesDomainName = self.validatesDomainName; 42 securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone]; 43 44 return securityPolicy; 45 }
好了,這篇就到這了,大體了解了SSL校驗是怎么一回事了,而且知道了該如何操作。
在這里推薦一篇不錯的文章 iOS 9之適配ATS