HTTPS就是將HTTP協議數據包放到SSL/TSL層加密后,在TCP/IP層組成IP數據報去傳輸,以此保證傳輸數據的安全。
證書分為兩種,一種是花錢向CA(證書認證的機構)購買的證書,服務端如果使用的是這類證書的話,那一般客戶端不需要做什么,用HTTPS進行請求就行了,蘋果內置了那些受信任的根證書的。另一種是自己制作的證書,使用這類證書的話是不受信任的(當然也不用花錢買),因此需要我們在代碼中將該證書設置為信任證書。
對于一般的小型網站尤其是博客,可以使用自簽名證書來構建安全網絡,所謂自簽名證書,就是自己扮演 CA 機構,自己給自己的服務器頒發證書。
SSL協議的握手過程:(參考)
SSL/TSl握手階段的詳細過程
- 首先,客戶端(通常是瀏覽器)先向服務器發出加密通信的請求,這被叫做ClientHello請求。
在這一步,客戶端主要向服務器提供以下信息。
(1) 支持的協議版本,比如TLS 1.0版。
(2) 一個客戶端生成的隨機數,稍后用于生成"對話密鑰"。
(3) 支持的加密方法,比如RSA公鑰加密。
(4) 支持的壓縮方法。 - 服務器收到客戶端請求后,向客戶端發出回應,這叫做SeverHello。服務器的回應包含以下內容。
(1) 確認使用的加密通信協議版本,比如TLS 1.0版本。如果瀏覽器與服務器支持的版本不一致,服務器關閉加密通信。
(2) 一個服務器生成的隨機數,稍后用于生成"對話密鑰"。
(3) 確認使用的加密方法,比如RSA公鑰加密。
(4) 服務器證書。 - 客戶端收到服務器回應以后,首先驗證服務器證書。如果證書不是可信機構頒布、或者證書中的域名與實際域名不一致、或者證書已經過期,就會向訪問者顯示一個警告,由其選擇是否還要繼續通信。
如果證書沒有問題,客戶端就會從證書中取出服務器的公鑰。然后,向服務器發送下面三項信息:
(1) 一個隨機數。該隨機數用服務器公鑰加密,防止被竊聽。
(2) 編碼改變通知,表示隨后的信息都將用雙方商定的加密方法和密鑰發送。
(3) 客戶端握手結束通知,表示客戶端的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供服務器校驗。
上面第一項的隨機數,是整個握手階段出現的第三個隨機數,又稱"pre-master key"。有了它以后,客戶端和服務器就同時有了三個隨機數,接著雙方就用事先商定的加密方法,各自生成本次會話所用的同一把"會話密鑰"。 - 服務器收到客戶端的第三個隨機數pre-master key之后,計算生成本次會話所用的"會話密鑰"。然后,向客戶端最后發送下面信息。
(1)編碼改變通知,表示隨后的信息都將用雙方商定的加密方法和密鑰發送。
(2)服務器握手結束通知,表示服務器的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供客戶端校驗。
至此,整個握手階段全部結束。接下來,客戶端與服務器進入加密通信,就完全是使用普通的HTTP協議,只不過用"會話密鑰"加密內容。
證書
證書的申請過程
- 證書申請者向頒發證書的可信第三方CA提交申請證書相關信息,包括:申請者域名、申請者生成的公鑰(私鑰自己保存)及證書請求文件.cer等
- CA通過線上、線下等多種手段驗證證書申請者提供的信息合法和真實性。
- 當證書申請者提供的信息審核通過后,CA向證書申請者頒發證書,證書內容包括明文信息和簽名信息。其中明文信息包括證書頒發機構、證書有效期、域名、申請者相關信息及申請者公鑰等,簽名信息是使用CA私鑰進行加密的明文信息。當證書申請者獲取到證書后,可以通過安裝的CA證書中的公鑰對簽名信息進行解密并與明文信息進行對比來驗證簽名的完整性。
證書的驗證過程
TSL/SSL的握手過程里client會拿到server返回的證書并且驗證證書本身的合法性(驗證簽名完整性,驗證證書有效期等)
- 驗證證書頒發者的合法性(查找頒發者的證書并檢查其合法性,這個過程是遞歸的)
- 證書驗證的遞歸過程最終會成功終止,而成功終止的條件是:證書驗證過程中遇到了錨點證書,錨點證書通常指:嵌入到操作系統中的根證書(權威證書頒發機構頒發的自簽名證書)。
證書驗證失敗的原因
- 無法找到證書的頒發者
- 證書過期
- 驗證過程中遇到了自簽名證書,但該證書不是錨點證書。
- 無法找到錨點證書(即在證書鏈的頂端沒有找到合法的根證書)
- 訪問的server的dns地址和證書中的地址不同
iOS實現支持HTTPS
在OC中當使用NSURLConnection
或NSURLSession
建立URL并向服務器發送https請求獲取資源時,服務器會使用HTTP狀態碼401進行響應(即訪問拒絕)。此時NSURLConnection或NSURLSession會接收到服務器需要授權的響應,當客戶端授權通過后,才能繼續從服務器獲取數據。如下圖所示:
非自簽名證書驗證實現
當客戶端發送https請求后,服務器會返回需要授權的相關信息,對于NSURLSession而言,,需要代理對象實現URLSession:task:didReceiveChallenge:completionHandler:
方法。
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == kSecTrustResultUnspecified || status == kSecTrustResultProceed) {
credential = [NSURLCredential credentialForTrust:trust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
}
}
completionHandler(disposition, credential);
}
-
NSURLSessionAuthChallengePerformDefaultHandling
處理請求,就好像代理沒有提供一個代理方法來處理認證請求 -
NSURLSessionAuthChallengeRejectProtectionSpace
拒接認證請求。基于服務器響應的認證類型,URL加載類可能會多次調用代理方法。
SecTrustRef
表示需要驗證的信任對象(Trust Object),在此指的是challenge.protectionSpace.serverTrust。包含待驗證的證書和支持的驗證方法等。
SecTrustResultType
表示驗證結果。其中 kSecTrustResultProceed表示serverTrust驗證成功,且該驗證得到了用戶認可(例如在彈出的是否信任的alert框中選擇always trust)。 kSecTrustResultUnspecified表示 serverTrust驗證成功,此證書也被暗中信任了,但是用戶并沒有顯示地決定信任該證書。 兩者取其一就可以認為對serverTrust驗證成功。
SecTrustEvaluate
函數內部遞歸地從葉節點證書到根證書驗證。使用系統默認的驗證方式驗證Trust Object,根據上述證書鏈的驗證可知,系統會根據Trust Object的驗證策略,一級一級往上,驗證證書鏈上每一級證書有效性。
對于非自簽名的證書,即使服務器返回的證書是信任的CA頒發的,假如有更強的安全要求,可以繼續對Trust Object進行更嚴格的驗證。常用的方式是在本地導入證書,并將導入的證書設置成需要參與驗證的錨點證書,再調用SecTrustEvaluate通過本地導入的證書來驗證服務器證書是否是可信的。如果服務器證書是這個錨點證書對應CA或者子CA頒發的,或服務器證書本身就是這個錨點證書,則證書信任通過。如下代碼:
NSString *cerPath = ...;//證書在本次的路徑
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)cerPath);
self.trustedCertificates = @[CFBridgingRelease(certificate)];
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
//將之前導入的證書設置成下面驗證的Trust Object的錨點證書
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);
//通過本地導入的證書來驗證服務器返回的證書是否可信
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == kSecTrustResultUnspecified || status == kSecTrustResultProceed) {
credential = [NSURLCredential credentialForTrust:trust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
}
}
completionHandler(disposition, credential);
}
自簽名證書
對于自簽名證書,這樣Trust Object中的服務器證書是不可信任的CA頒發的,直接使用SecTrustEvaluate驗證是不會成功的。
可以采取下述簡單代碼繞過HTTPS的驗證:
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
綜上對非自建和自建證書驗證過程的分析,可以總結如下:
- 獲取需要驗證的信任對象(Trust Object)。對于NSURLSession來說,
是從delegate方法URLSession:task:didReceiveChallenge:completionHandler:
回調回來的參數challenge中獲取(challenge.protectionSpace.serverTrust) 。
使用系統默認驗證方式驗證Trust Object。 - SecTrustEvaluate會根據Trust Object的驗證策略,一級一級往上,驗證證書鏈上每一級數字簽名的有效性,從而評估證書的有效性。
- 如第二步驗證通過了,一般的安全要求下,就可以直接驗證通過,進入到下一步:使用Trust Object生成一份憑證([NSURLCredential credentialForTrust:serverTrust]),傳入block中 completionHandler(disposition, credential)處理,建立連接。
- 假如有更強的安全要求,可以繼續對Trust Object進行更嚴格的驗證。常用的方式是在本地導入證書,驗證Trust Object與導入的證書是否匹配。
- 假如驗證失敗,取消此次Challenge-Response Authentication驗證流程,拒絕連接請求。
- 假如是自建證書的,則不使用第二步系統默認的驗證方式,因為自建證書的根CA的數字簽名未在操作系統的信任列表中。
在AFNetwoking的AFURLSessionManager.m這個文件里實現了代理方法URLSession:task:didReceiveChallenge:completionHandler:
并且在代理方法里對服務器返回的證書做了驗證。驗證的方法是evaluateServerTrust:forDomain:(NSString *)domain
。
AFNetwoking提供了一個類AFSecurityPolicy
,它有一個屬性:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
這個枚舉值決定了按哪種方式來驗證服務器返回的證書。默認值是AFSSLPinningModeNone
。
- AFSSLPinningModeNone:這會通過SecTrustEvaluate來驗證服務器返回的證書,對于CA認證的證書可以選用這個值;
- AFSSLPinningModeCertificate:如果使用了CA認證的證書,并且在本地也導入了證書作為錨點證書來驗證服務器返回的證書,可以用這個值。