簡體   English   中英

出於開發目的,如何使用 iOS 7 的 NSURLSession 及其委托方法系列接受自簽名 SSL 證書?

[英]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 驗證成功,而不會危及您自己的任何其他設備(並且僅在那時,暫時)。

對於 Swift 3.0 / 4

如果您只想允許任何類型的自簽名證書,您可以使用以下方法來實現 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()

現在實現委托的方法https://developer.apple.com/documentation/foundation/nsurlsessiondelegate/1409308-urlsession?language=objc

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM