[英]How do I accept a self-signed SSL certificate using iOS 7's NSURLSession and its family of delegate methods for development purposes?
我正在開發一個 iPhone 應用程序。 在開發期間,我需要連接到使用自簽名 SSL 證書的服務器。 我很確定- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
是我編寫一些異常代碼來實現這一點的機會。 但是,我找不到任何資源可以告訴我如何執行此操作。 我可以在日志中看到以下錯誤:
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
除此之外,當我NSLog(@"error = %@", error);
從上面的委托方法中我得到:
錯誤域=NSURLErrorDomain 代碼=-1202 “此服務器的證書無效。您可能正在連接到偽裝成
api.mydevelopmenturl.example
的服務器,這可能會使您的機密信息面臨風險。” UserInfo=0x10cbdbcf0 {NSUnderlyingError=0x112ec9730 "此服務器的證書無效。您可能正在連接到偽裝成api.mydevelopmenturl.example
的服務器,這可能會使您的機密信息面臨風險。", NSErrorFailingURLStringKey=https:// api.mydevelopmenturl.example/posts, NSErrorFailingURLKey=https://api.mydevelopmenturl.example/posts, NSLocalizedRecoverySuggestion=您仍然想連接到服務器嗎?, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x112e5a020>, NSLocalizedDescription=此服務器的證書是無效的。 您可能正在連接到偽裝成api.mydevelopmenturl.example
的服務器,這可能會使您的機密信息面臨風險。}
關於如何解決這個問題的任何想法? 請發布代碼,因為我已經閱讀了概念文檔,但我不理解它們。 這是我無法理解的示例: https ://developer.apple.com/library/content/technotes/tn2232/_index.html
這對我有用:
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
...
...
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:@"mydomain.example"]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
Apple 有一份技術說明 2232 ,內容豐富,詳細解釋了HTTPS 服務器信任評估。
在這種情況下, NSURLErrorDomain
域中的錯誤 -1202 是NSURLErrorServerCertificateUntrusted
,這意味着服務器信任評估失敗。 您可能還會收到各種其他錯誤; 附錄 A:常見服務器信任評估錯誤列出了最常見的錯誤。
從技術說明:
在大多數情況下,解決服務器信任評估失敗的最佳方法是修復服務器。 這有兩個好處:它提供了最好的安全性,它減少了你必須編寫的代碼量。 本技術說明的其余部分描述了如何診斷服務器信任評估失敗,以及如果無法修復服務器,如何自定義服務器信任評估以允許您的連接繼續進行,而不會完全破壞用戶的安全。
與這個問題密切相關的特定部分是關於NSURLSession 服務器信任評估的部分:
NSURLSession
允許您通過實現-URLSession:didReceiveChallenge:completionHandler:
委托方法來自定義 HTTPS 服務器信任評估。 要自定義 HTTPS 服務器信任評估,請查找其保護空間具有NSURLAuthenticationMethodServerTrust
身份驗證方法的質詢。 對於這些挑戰,請按如下所述解決它們。 對於其他挑戰,您不關心的挑戰,使用NSURLSessionAuthChallengePerformDefaultHandling
處置和 NULL 憑證調用完成處理程序塊。在處理 NSURLAuthenticationMethodServerTrust 身份驗證質詢時,您可以通過調用 -serverTrust 方法從質詢的保護空間中獲取信任對象。 使用信任對象執行您自己的自定義 HTTPS 服務器信任評估后,您必須通過以下兩種方式之一解決質詢:
如果要拒絕連接,請使用
NSURLSessionAuthChallengeCancelAuthenticationChallenge
處置和 NULL 憑據調用完成處理程序塊。如果您想允許連接,請從您的信任對象創建一個憑證(使用
+[NSURLCredential credentialForTrust:]
)並使用該憑證和NSURLSessionAuthChallengeUseCredential
處置調用完成處理程序塊。
所有這一切的結果是,如果您實現以下委托方法,您可以覆蓋特定服務器的服務器信任:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
if([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust])
{
if([challenge.protectionSpace.host
isEqualToString:@"domaintooverride.example"])
{
NSURLCredential *credential =
[NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
else
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
請注意,您必須同時處理與您要覆蓋的主機匹配的情況以及所有其他情況。 如果您不處理“所有其他情況”部分,則行為結果是未定義的。
在線查找可提供 90 天免費試用新證書的可信 SSL 證書頒發機構。 在您的服務器上安裝證書。 您現在有 90 天的時間來開發您的應用程序,直到您可以決定是否值得花錢“更新”證書。 這對我來說是最好的答案,因為我決定使用自簽名證書是出於經濟動機,並且 90 天給了我足夠的時間來開發我的應用程序,直到我可以決定是否值得花錢購買 SSL 證書. 這種方法避免了必須處理運行代碼庫的安全隱患,該代碼庫經過調整以接受自簽名證書。 甜的! 是的引導!
幫自己一個大忙,不要。
首先閱讀論文世界上最危險的代碼:在非瀏覽器軟件中驗證 SSL 證書,特別是第 10 節“破壞或禁用證書驗證”。 它專門調用了一個與 Cocoa 相關的博客,該博客專門描述了如何按照您的要求進行操作。
但是不要。 禁用 SSL 證書檢查就像在您的應用程序中引入了定時炸彈。 某天,某天,它會意外地被啟用,並且構建將進入野外。 在那一天,您的用戶將面臨嚴重風險。
相反,您應該使用一個使用中間證書簽名的證書,您可以在該特定設備上安裝和信任該證書,這將允許 SSL 驗證成功,而不會危及您自己的任何其他設備(並且僅在那時,暫時)。
如果您只想允許任何類型的自簽名證書,您可以使用以下方法來實現 URLSessionDelegate。 Apple 提供了有關如何將 URLSessionDelegate 用於各種身份驗證方法的附加信息: https ://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html
首先實現委托方法並分配一個相應的委托:
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let task = urlSession.dataTask(with: urlRequest).resume()
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.previousFailureCount == 0 else {
challenge.sender?.cancel(challenge)
// Inform the user that the user name and password are incorrect
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// Within your authentication handler delegate method, you should check to see if the challenge protection space has an authentication type of NSURLAuthenticationMethodServerTrust
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
// and if so, obtain the serverTrust information from that protection space.
&& challenge.protectionSpace.serverTrust != nil
&& challenge.protectionSpace.host == "yourdomain.com" {
let proposedCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, proposedCredential)
}
}
不過,您可以調整對您提供的域的任何自簽名證書的接受,以匹配一個非常具體的證書。 確保您之前將此證書添加到您的構建目標包中。 我在這里將其命名為“cert.cer”
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.previousFailureCount == 0 else {
challenge.sender?.cancel(challenge)
// Inform the user that the user name and password are incorrect
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
&& challenge.protectionSpace.serverTrust != nil
&& challenge.protectionSpace.host == "yourdomain.com" {
if let trust = challenge.protectionSpace.serverTrust,
let pem = Bundle.main.url(forResource:"cert", withExtension: "cer"),
let data = NSData(contentsOf: pem),
let cert = SecCertificateCreateWithData(nil, data) {
let certs = [cert]
SecTrustSetAnchorCertificates(trust, certs as CFArray)
var result=SecTrustResultType.invalid
if SecTrustEvaluate(trust,&result)==errSecSuccess {
if result==SecTrustResultType.proceed || result==SecTrustResultType.unspecified {
let proposedCredential = URLCredential(trust: trust)
completionHandler(.useCredential,proposedCredential)
return
}
}
}
}
completionHandler(.performDefaultHandling, nil)
}
與 friherd 的解決方案相同,但速度很快:
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
}
}
只需將 .cer 添加到 SecTrust 並傳遞 ATS
class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
if let trust = challenge.protectionSpace.serverTrust,
let pem = Bundle.main.path(forResource: "https", ofType: "cer"),
let data = NSData(contentsOfFile: pem),
let cert = SecCertificateCreateWithData(nil, data) {
let certs = [cert]
SecTrustSetAnchorCertificates(trust, certs as CFArray)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: trust))
return
}
}
// Pinning failed
completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
}
}
更新xcode 9
var result:(message:String, data:Data?) = (message: "Fail", data: nil)
var request = URLRequest(url: url)
let sessionDelegate = SessionDelegate()
let session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
let task = session.dataTask(with: request){(data, response, error) in
}
task.resume()
委托任務
class SessionDelegate:NSObject, URLSessionDelegate
{
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
{
print(challenge.protectionSpace.host)
if(challenge.protectionSpace.host == "111.11.11.11")
{
let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
}
}
}
}
這是對我有用的解決方案。 您需要通過連接的委托接受連接,包括兩條消息:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
請注意,這樣做並沒有檢查證書的可信度,因此只有 HTTPS 連接的 SSL 加密是有趣的,但這里沒有考慮簽名權限,這會降低安全性。
這對我來說很好,可以通過自簽名:
Delegate : NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session **task**:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
也許更好的方法是為用戶提供接受證書的機會,以確認(視覺上)該 URL 對於正在訪問的服務是准確的。 例如,如果主機被輸入到某個應用程序設置中,請在用戶的輸入處進行測試,並讓用戶在那里做出決定。
考慮到這種“用戶確認”策略被 Safari 使用,因此被 Apple 縱容,它在邏輯上適用於其他應用程序是有道理的。
建議深入研究 NSErrorRecoveryAttempting(我自己不做) http://apple.co/22Au1GR
確認主機,然后采取本文中提到的個人 URL 排除路線。 根據實現,將主機存儲為排除項以供將來參考也可能有意義。
這似乎是 Apple 在 Cocoa 中自然實現的功能,但到目前為止,我還沒有找到一個“簡單的按鈕”。 本來希望在 NSURL 或 NSURLSession 中的某些東西上有一個“kLetUserDecide”標志,而不是每個人都必須實現委托方法以及 NSErrorRecoveryAttempting 協議。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.