SSL(Secure Sockets Layer, 安全套接字層),由於原先互聯網上使用的HTTP協議是明文的,存在不少缺點,好比傳輸內容會被偷窺和篡改。SSL協議的做用就是在傳輸層對網絡鏈接進行加密。html
到了1999年,SSL 由於應用普遍,已經成爲互聯網上的事實標準。IETF就在那年把SSL標準化。標準化以後的名稱改成 TLS(Transport Layer Security,傳輸層安全協議)。SSL與TLS能夠視做同一個東西的不一樣階段。git
簡單來講,HTTPS = HTTP + SSL/TLS, 也就是HTTP over SSL
或HTTP over TLS
,這是後面加S的由來。github
HTTPS和HTTP異同:HTTP和HTTPS使用的是徹底不一樣的鏈接方式,用的端口也不同,前者是80,後者是443。HTTP的鏈接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比HTTP協議安全。數組
首先,客戶端(一般是瀏覽器)先向服務器發出加密通訊的請求,這被叫作ClientHello請求。在這一步,客戶端主要向服務器提供如下信息。瀏覽器
(1)支持的協議版本,好比TLS1.0版。安全
(2)一個客戶端生成的隨機數,稍後用於生成"對話密鑰"。服務器
(3)支持的加密方法,好比RSA公鑰加密。網絡
(4)支持的壓縮方法。架構
服務器收到客戶端請求後,向客戶端發出迴應,這叫作SeverHello。服務器的迴應包含如下內容。app
(1)確認使用的加密通訊協議版本,好比TLS1.0版本。若是瀏覽器與服務器支持的版本不一致,服務器關閉加密通訊。
(2)一個服務器生成的隨機數,稍後用於生成"對話密鑰"。
(3)確認使用的加密方法,好比RSA公鑰加密。
(4)服務器證書。
客戶端收到服務器迴應之後,首先驗證服務器證書。若是證書不是可信機構頒佈、或者證書中的域名與實際域名不一致、或者證書已通過期,就會向訪問者顯示一個警告,由其選擇是否還要繼續通訊。若是證書沒有問題,客戶端就會從證書中取出服務器的公鑰。而後,向服務器發送下面三項信息。
(1)一個隨機數。該隨機數用服務器公鑰加密,防止被竊聽。
(2)編碼改變通知,表示隨後的信息都將用雙方商定的加密方法和密鑰發送。
(3)客戶端握手結束通知,表示客戶端的握手階段已經結束。這一項同時也是前面發送的全部內容的hash值,用來供服務器校驗。
上面第一項的隨機數,是整個握手階段出現的第三個隨機數,又稱"pre-master key"。有了它之後,客戶端和服務器就同時有了三個隨機數,接着雙方就用事先商定的加密方法,各自生成本次會話所用的同一把"會話密鑰"。
服務器收到客戶端的第三個隨機數pre-master key以後,計算生成本次會話所用的"會話密鑰"。而後,向客戶端最後發送下面信息。
(1)編碼改變通知,表示隨後的信息都將用雙方商定的加密方法和密鑰發送。
(2)服務器握手結束通知,表示服務器的握手階段已經結束。這一項同時也是前面發送的全部內容的hash值,用來供客戶端校驗。
至此,整個握手階段所有結束。接下來,客戶端與服務器進入加密通訊,就徹底是使用普通的HTTP協議,只不過用"會話密鑰"加密內容。
上面握手階段的第二步服務器給客戶端的證書就是數字證書,該證書包含了公鑰等信息,通常是由服務器發給客戶端,接收方經過驗證這個證書是否是由信賴的CA簽發,或者與本地的證書相對比,來判斷證書是否可信;假如須要雙向驗證,則服務器和客戶端都須要發送數字證書給對方驗證。
數字證書是一個電子文檔,其中包含了持有者的信息、公鑰以及證實該證書有效的數字簽名。而數字證書以及相關的公鑰管理和驗證等技術組成了PKI(公鑰基礎設施)規範體系。通常來講,數字證書是由數字證書認證機構(Certificate authority,即CA)來負責簽發和管理,並承擔PKI體系中公鑰合法性的檢驗責任;數字證書的類型有不少,而HTTPS使用的是SSL證書。
怎麼來驗證數字證書是由CA簽發的,而不是第三方僞造的呢? 在回答這個問題前,咱們須要先了解CA的組織結構。首先,CA組織結構中,最頂層的就是根CA,根CA下能夠受權給多個二級CA,而二級CA又能夠受權多個三級CA,因此CA的組織結構是一個樹結構。對於SSL證書市場來講,主要被Symantec(旗下有VeriSign和GeoTrust)、Comodo SSL、Go Daddy 和 GlobalSign 瓜分。 瞭解了CA的組織結構後,來看看數字證書的簽發流程:
數字證書的簽發機構CA,在接收到申請者的資料後進行覈對並肯定信息的真實有效,而後就會製做一份符合X.509標準的文件。證書中的證書內容包括了持有者信息和公鑰等都是由申請者提供的,而數字簽名則是CA機構對證書內容進行hash加密後等到的,而這個數字簽名就是咱們驗證證書是不是有可信CA簽發的數據。
接收端接到一份數字證書Cer1後,對證書的內容作Hash等到H1;而後在簽發該證書的機構CA1的數字證書中找到公鑰,對證書上數字簽名進行解密,獲得證書Cer1簽名的Hash摘要H2;對比H1和H2,假如相等,則表示證書沒有被篡改。但這個時候仍是不知道CA是不是合法的,咱們看到上圖中有CA機構的數字證書,這個證書是公開的,全部人均可以獲取到。而這個證書中的數字簽名是上一級生成的,因此能夠這樣一直遞歸驗證下去,直到根CA。根CA是自驗證的,即他的數字簽名是由本身的私鑰來生成的。合法的根CA會被瀏覽器和操做系統加入到權威信任CA列表中,這樣就完成了最終的驗證。因此,必定要保護好本身環境(瀏覽器/操做系統)中根CA信任列表,信任了根CA就表示信任全部根CA下全部子級CA所簽發的證書,不要隨便添加根CA證書。
能夠理解爲證書綁定,是指客戶端直接保存服務端的證書,創建https鏈接時直接對比服務端返回的和客戶端保存的兩個證書是否同樣,同樣就代表證書是真的,再也不去系統的信任證書機構裏尋找驗證。這適用於非瀏覽器應用,由於瀏覽器跟不少未知服務端打交道,沒法把每一個服務端的證書都保存到本地,但CS架構的像手機APP事先已經知道要進行通訊的服務端,能夠直接在客戶端保存這個服務端的證書用於校驗。
爲何直接對比就能保證證書沒問題?若是中間人從客戶端取出證書,再假裝成服務端跟其餘客戶端通訊,它發送給客戶端的這個證書不就能經過驗證嗎?確實能夠經過驗證,但後續的流程走不下去,由於下一步客戶端會用證書裏的公鑰加密,中間人沒有這個證書的私鑰就解不出內容,也就截獲不到數據,這個證書的私鑰只有真正的服務端有,中間人僞造證書主要僞造的是公鑰。
爲何要用SSL Pinning?正常的驗證方式不夠嗎?若是服務端的證書是從受信任的的CA機構頒發的,驗證是沒問題的,但CA機構頒發證書比較昂貴,小企業或我的用戶可能會選擇本身頒發證書,這樣就沒法經過系統受信任的CA機構列表驗證這個證書的真僞了,因此須要SSL Pinning這樣的方式去驗證。
下面我會實現自簽名證書(12306)、SSL信任證書(baidu)、系統證書(蘋果)三種狀況的實現來看他們的區別,百度和12306的證書已經被我下載到個人項目裏面了,具體能夠去Demo裏面看實現過程。
咱們手動指定securityPolicy
認證屬性。經過12306證書來實現。
//自建證書認證 - (IBAction)buttion1:(id)sender { NSURL *url = [NSURL URLWithString:@"https://kyfw.12306.cn/otn/leftTicket/init"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; // [request setValue:@"text/html" forHTTPHeaderField:@"Accept"]; AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; //指定安全策略 manager.securityPolicy = [self ticketSecurityPolicy]; //指定返回數據類型,默認是AFJSONResponseSerializer類型,猶豫這裏不是JSON類型的返回數據,因此須要手動指定返回類型 AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer]; responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"]; manager.responseSerializer = responseSerializer; NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error); }]; [dataTask resume]; } /** 12306的認證證書,他的認證證書是自簽名的 @return 返回指定的認證策略 */ -(AFSecurityPolicy*)ticketSecurityPolicy { // /先導入證書 NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"];//證書的路徑 NSData *certData = [NSData dataWithContentsOfFile:cerPath]; NSSet *set = [NSSet setWithObject:certData]; AFSecurityPolicy *securityPolicy; if (true) { securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set]; }else{ // AFSSLPinningModeCertificate 使用證書驗證模式。下面這個方法會默認使用項目裏面的全部證書 securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; } // allowInvalidCertificates 是否容許無效證書(也就是自建的證書),默認爲NO // 若是是須要驗證自建證書,須要設置爲YES securityPolicy.allowInvalidCertificates = YES; //validatesDomainName 是否須要驗證域名,默認爲YES; //假如證書的域名與你請求的域名不一致,需把該項設置爲NO;如設成NO的話,即服務器使用其餘可信任機構頒發的證書,也能夠創建鏈接,這個很是危險,建議打開。 //置爲NO,主要用於這種狀況:客戶端請求的是子域名,而證書上的是另一個域名。由於SSL證書上的域名是獨立的,假如證書上註冊的域名是www.google.com,那麼mail.google.com是沒法驗證經過的;固然,有錢能夠註冊通配符的域名*.google.com,但這個仍是比較貴的。 //如置爲NO,建議本身添加對應域名的校驗邏輯。 securityPolicy.validatesDomainName = NO; return securityPolicy; }
咱們手動指定securityPolicy
認證屬性。經過百度證書來實現。
//認證證書認證 - (IBAction)button2:(id)sender { NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; //[request setValue:@"text/html" forHTTPHeaderField:@"Accept"]; AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; //指定安全策略 manager.securityPolicy = [self baiduSecurityPolicy]; //指定返回數據類型,默認是AFJSONResponseSerializer類型,猶豫這裏不是JSON類型的返回數據,因此須要手動指定返回類型 AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer]; responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"]; manager.responseSerializer = responseSerializer; NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error); }]; [dataTask resume]; } /** 百度的的認證證書,他的認證證書是花錢買的,也就是否是自簽名的證書。這種證書,若是咱們要手動指定,pinmode只能是`AFSSLPinningModeNone` @return 返回指定的認證策略 */ -(AFSecurityPolicy*)baiduSecurityPolicy { // /先導入證書 NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"baidu" ofType:@"cer"];//證書的路徑 NSData *certData = [NSData dataWithContentsOfFile:cerPath]; NSSet *set = [NSSet setWithObject:certData]; AFSecurityPolicy *securityPolicy; if (true) { //這裏只能用AFSSLPinningModeNone才能成功,並且我係統的證書列表裏面已經有百度的證書了 securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone withPinnedCertificates:set]; }else{ // AFSSLPinningModeCertificate 使用證書驗證模式。下面這個方法會默認使用項目裏面的全部證書 securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; } // allowInvalidCertificates 是否容許無效證書(也就是自建的證書),默認爲NO // 若是是須要驗證自建證書,須要設置爲YES securityPolicy.allowInvalidCertificates = NO; //validatesDomainName 是否須要驗證域名,默認爲YES; //假如證書的域名與你請求的域名不一致,需把該項設置爲NO;如設成NO的話,即服務器使用其餘可信任機構頒發的證書,也能夠創建鏈接,這個很是危險,建議打開。 //置爲NO,主要用於這種狀況:客戶端請求的是子域名,而證書上的是另一個域名。由於SSL證書上的域名是獨立的,假如證書上註冊的域名是www.google.com,那麼mail.google.com是沒法驗證經過的;固然,有錢能夠註冊通配符的域名*.google.com,但這個仍是比較貴的。 //如置爲NO,建議本身添加對應域名的校驗邏輯。 securityPolicy.validatesDomainName = YES; return securityPolicy; }
這裏咱們不作任何額外的處理,直接使用AFN的默認證書處理機制。經過AFURLSessionManager
的securityPolicy
默認實現。它會和存在系統中的作對比來驗證證書。
//系統證書認證 - (IBAction)button3:(id)sender { NSURL *url = [NSURL URLWithString:@"https://www.apple.com/"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; //指定返回數據類型,默認是AFJSONResponseSerializer類型,猶豫這裏不是JSON類型的返回數據,因此須要手動指定返回類型 AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer]; responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"]; manager.responseSerializer = responseSerializer; NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error); }]; [dataTask resume]; }
AFSSLPinningModeNone:
這個模式表示不作SSL pinning,只跟瀏覽器同樣在系統的信任機構列表裏驗證服務端返回的證書。若證書是信任機構簽發的就會經過,如果本身服務器生成的證書,這裏是不會經過的。
AFSSLPinningModeCertificate:
這個模式表示用證書綁定方式驗證證書,須要客戶端保存有服務端的證書拷貝,這裏驗證分兩步,第一步驗證證書的域名/有效期等信息,第二步是對比服務端返回的證書跟客戶端返回的是否一致。這裏還沒弄明白第一步的驗證是怎麼進行的,代碼上跟去系統信任機構列表裏驗證同樣調用了SecTrustEvaluate,只是這裏的列表換成了客戶端保存的那些證書列表。若要驗證這個,是否應該把服務端證書的頒發機構根證書也放到客戶端裏?
AFSSLPinningModePublicKey:
這個模式一樣是用證書綁定方式驗證,客戶端要有服務端的證書拷貝,只是驗證時只驗證證書裏的公鑰,不驗證證書的有效期等信息。只要公鑰是正確的,就能保證通訊不會被竊聽,由於中間人沒有私鑰,沒法解開經過公鑰加密的數據。
這是一個須要驗證的信任對象,包含待驗證的證書和支持的驗證方法等。
表示驗證結果。其中 kSecTrustResultProceed表示serverTrust驗證成功,且該驗證獲得了用戶承認(例如在彈出的是否信任的alert框中選擇always trust)。 kSecTrustResultUnspecified表示 serverTrust驗證成功,此證書也被暗中信任了,可是用戶並無顯示地決定信任該證書。 二者取其一就能夠認爲對serverTrust驗證成功。
證書校驗函數,在函數的內部遞歸地從葉節點證書到根證書驗證。須要驗證證書自己的合法性(驗證簽名完整性,驗證證書有效期等);驗證證書頒發者的合法性(查找頒發者的證書並檢查其合法性,這個過程是遞歸的).而遞歸的終止條件是證書驗證過程當中遇到了錨點證書(錨點證書:嵌入到操做系統中的根證書,這個根證書是權威證書頒發機構頒發的自簽名證書)。
AFSecurityPolicy
的源碼細節以下:
/** 證書的驗證類型 - AFSSLPinningModeNone: 不使用`pinned certificates`來驗證證書 - AFSSLPinningModePublicKey: 使用`pinned certificates`來驗證證書的公鑰 - AFSSLPinningModeCertificate: 使用`pinned certificates`來驗證整個證書 */ typedef NS_ENUM(NSUInteger, AFSSLPinningMode) { AFSSLPinningModeNone, AFSSLPinningModePublicKey, AFSSLPinningModeCertificate, }; /** 獲取指定證書的公鑰 @param certificate 證書數據 @return 公鑰 */ static id AFPublicKeyForCertificate(NSData *certificate) { id allowedPublicKey = nil; SecCertificateRef allowedCertificate; SecPolicyRef policy = nil; SecTrustRef allowedTrust = nil; SecTrustResultType result; //獲取證書對象 allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); __Require_Quiet(allowedCertificate != NULL, _out); //獲取X.509的認證策略 policy = SecPolicyCreateBasicX509(); //獲取allowedTrust對象的值 __Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out); __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out); //根據allowedTrust獲取對應的公鑰 allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust); //C++的gumpto跳轉,當前面的操做出錯之後,直接跳入_out執行 _out: if (allowedTrust) { CFRelease(allowedTrust); } if (policy) { CFRelease(policy); } if (allowedCertificate) { CFRelease(allowedCertificate); } //返回公鑰 return allowedPublicKey; } /** 在指定的證書和認證策略下,驗證SecTrustRef對象是不是受信任的、合法的。 @param serverTrust SecTrustRef對象 @return 結果 */ static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) { BOOL isValid = NO; SecTrustResultType result; //獲取serverTrust的認證結果,調用`SecTrustEvaluate`表示經過系統的證書來比較認證 __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); _out: return isValid; } /** 根據`serverTrust`獲取認證的證書鏈 @param serverTrust serverTrust對象 @return 認證證書鏈 */ static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) { //獲取認證鏈的總層次 CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust); NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount]; //獲取每一級認證鏈,把獲取的證書數據存入數組中 for (CFIndex i = 0; i < certificateCount; i++) { SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i); [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)]; } //返回證書鏈數組 return [NSArray arrayWithArray:trustChain]; } /** 獲取serverTrust對象的認證鏈的公鑰數組 @param serverTrust serverTrust對象 @return 公鑰數組 */ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { //X.509標準的安全策略 SecPolicyRef policy = SecPolicyCreateBasicX509(); //獲取證書鏈的證書數量 CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust); NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount]; for (CFIndex i = 0; i < certificateCount; i++) { SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i); SecCertificateRef someCertificates[] = {certificate}; CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL); SecTrustRef trust; //經過一個證書、認證策略新建一個SecTrustRef對象 __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out); SecTrustResultType result; //驗證SecTrustRef對象是否成功 __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out); //把SecTrustRef對應的公鑰加入數組中 [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)]; _out: if (trust) { CFRelease(trust); } if (certificates) { CFRelease(certificates); } continue; } CFRelease(policy); return [NSArray arrayWithArray:trustChain]; } #pragma mark - @interface AFSecurityPolicy() //認證策略 @property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode; //公鑰集合 @property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys; @end @implementation AFSecurityPolicy /** 從MainBundle中獲取全部證書 @param bundle 返回包含在bundle中的證書集合。若是AFNetworking使用的是靜態庫,咱們必須經過這個方法來加載證書。而且經過`policyWithPinningMode:withPinnedCertificates`方法來指定認證類型。 @return 返回bundle裏面的證書 */ + (NSSet *)certificatesInBundle:(NSBundle *)bundle { //獲取項目裏的全部.cer證書 NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]]; for (NSString *path in paths) { //獲取證書對應的NSData,而且加入集合中 NSData *certificateData = [NSData dataWithContentsOfFile:path]; [certificates addObject:certificateData]; } //返回證書集合 return [NSSet setWithSet:certificates]; } /** 返回當前類所在bundle所在的證書集合 @return 證書集合 */ + (NSSet *)defaultPinnedCertificates { static NSSet *_defaultPinnedCertificates = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //獲取當前類所在bundle NSBundle *bundle = [NSBundle bundleForClass:[self class]]; _defaultPinnedCertificates = [self certificatesInBundle:bundle]; }); return _defaultPinnedCertificates; } /** 返回默認的安全認證策略,在這裏是驗證系統的證書。這個策略不容許非法證書、驗證主機名、不驗證證書內容和公鑰 @return 返回認證策略 */ + (instancetype)defaultPolicy { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = AFSSLPinningModeNone; return securityPolicy; } /** 根據指定的認證策略和默認的證書列表初始化一個`AFSecurityPolicy`對象 @param pinningMode 認證策略 @return `AFSecurityPolicy`對象 */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode { return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]]; } /** 經過制定的認證策略`pinningMode`和證書集合`pinnedCertificates`來初始化一個`AFSecurityPolicy`對象 @param pinningMode 認證模型 @param pinnedCertificates 證書集合 @return AFSecurityPolicy對象 */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = pinningMode; //設置`_pinnedCertificates`和`pinnedPublicKeys`屬性,分別對應證書集合和公鑰集合 [securityPolicy setPinnedCertificates:pinnedCertificates]; //返回初始化成功的`AFSecurityPolicy` return securityPolicy; } - (instancetype)init { self = [super init]; if (!self) { return nil; } //默認是要認證主機名稱 self.validatesDomainName = YES; return self; } /** 經過指定的證書結合獲取到對應的公鑰集合。而後賦值給`pinnedPublicKeys`屬性 @param pinnedCertificates 證書集合 */ - (void)setPinnedCertificates:(NSSet *)pinnedCertificates { _pinnedCertificates = pinnedCertificates; if (self.pinnedCertificates) { NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]]; //迭代每個證書 for (NSData *certificate in self.pinnedCertificates) { //獲取證書對應的公鑰 id publicKey = AFPublicKeyForCertificate(certificate); if (!publicKey) { continue; } [mutablePinnedPublicKeys addObject:publicKey]; } //賦值給對應的屬性 self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys]; } else { self.pinnedPublicKeys = nil; } } #pragma mark - /** 爲serverTrust對象指定認證策略,若是domain不爲nil,則包括對主機名的認證。這個方法必須在接受到`authentication challenge`返回的時候調用。 SecTrustRef能夠理解爲橋接證書與認證策略的對象,他關聯指定的證書與認證策略 @param serverTrust 服務器的X.509標準的證書數據 @param domain 認證服務器的主機名。若是是nil,則不會對主機名進行認證。 @return serverTrust是否經過認證 */ - (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()]; } //給serverTrust對象指定認證策略 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)]; } //把證書與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) //獲取serverTrust證書鏈。直到根證書。 NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); //若是`pinnedCertificates`包含`serverTrust`對象對應的證書鏈的根證書。則返回true for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { if ([self.pinnedCertificates containsObject:trustChainCertificate]) { return YES; } } return NO; } case AFSSLPinningModePublicKey: {//只驗證證書裏面的數字簽名 NSUInteger trustedPublicKeyCount = 0; //根據serverTrust對象和SecPolicyCreateBasicX509認證策略,獲取對應的公鑰集合 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; } @end