web-dev-qa-db-ja.com

開発目的でiOS 7のNSURLSessionとデリゲートメソッドのファミリーを使用して、自己署名SSL証明書を受け入れるにはどうすればよいですか?

IPhoneアプリを開発しています。 開発中、自己署名SSL証明書を使用しているサーバーに接続する必要があります。 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandlerは、これを許可するための例外コードを書く機会です。ただし、これを行う方法を教えてくれるリソースは見つかりません。ログで次のエラーを確認できます。

NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

これに加えて、上記のデリゲートメソッド内からNSLog(@"error = %@", error);を取得すると、次のようになります。

Error Domain = NSURLErrorDomain Code = -1202 "このサーバーの証明書は無効です。" api.mydevelopmenturl.com "のふりをしているサーバーに接続している可能性があり、機密情報が危険にさらされる可能性があります。 UserInfo = 0x10cbdbcf0 {NSUnderlyingError = 0x112ec9730 "このサーバーの証明書は無効です。「api.mydevelopmenturl.com」のふりをしているサーバーに接続している可能性があり、機密情報が危険にさらされる可能性があります。 https://api.mydevelopmenturl.com/posts 、NSErrorFailingURLKey = https://api.mydevelopmenturl.com/posts 、NSLocalizedRecoverySuggestion =サーバーに接続しますか? 、NSURLErrorFailingURLPeerTrustErrorKey =、NSLocalizedDescription =このサーバーの証明書は無効です。 「api.mydevelopmenturl.com」のふりをしているサーバーに接続している可能性があります。これにより、機密情報が危険にさらされる可能性があります。}

この問題を解決する方法についてのアイデアはありますか?概念的なドキュメントを読んだので理解できないので、コードを投稿してください。私の向こうにある例: https://developer.Apple.com/library/content/technotes/tn2232/_index.html

57
John Erck

これは私のために働く:

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.com"]){
      NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
      completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    }
  }
}
110
friherd

Appleには テクニカルノート2232 があり、これは非常に有益であり、詳細に説明されています HTTPSサーバーの信頼評価

この場合、NSURLErrorDomainドメインのエラー-1202はNSURLErrorServerCertificateUntrustedです。これは、サーバーの信頼評価が失敗したことを意味します。また、他のさまざまなエラーを受け取る場合があります。 付録A:一般的なサーバー信頼評価エラー は、最も一般的なものをリストします。

テクニカルノートから:

ほとんどの場合、サーバーの信頼評価の失敗を解決する最善の方法は、サーバーを修正することです。これには2つの利点があります。最高のセキュリティを提供することと、記述しなければならないコードの量を減らすことです。このテクニカルノートの残りの部分では、サーバーの信頼評価の失敗を診断する方法、およびサーバーを修正できない場合は、サーバーの信頼評価をカスタマイズして、ユーザーのセキュリティを完全に損なうことなく接続を続行できるようにする方法について説明します。

この質問と密接な関係がある特定のビットは、 NSURLSessionサーバーの信頼評価 のセクションです。

NSURLSessionを使用すると、-URLSession:didReceiveChallenge:completionHandler:デリゲートメソッドを実装して、HTTPSサーバーの信頼評価をカスタマイズできます。 HTTPSサーバーの信頼評価をカスタマイズするには、保護スペースにNSURLAuthenticationMethodServerTrustの認証方法があるチャレンジを探します。これらの課題については、以下で説明するように解決します。気にしない他のチャレンジについては、NSURLSessionAuthChallengePerformDefaultHandlingディスポジションとNULLクレデンシャルで完了ハンドラーブロックを呼び出します。

NSURLAuthenticationMethodServerTrust認証チャレンジを処理する場合、-serverTrustメソッドを呼び出すことにより、チャレンジの保護スペースから信頼オブジェクトを取得できます。信頼オブジェクトを使用して独自のカスタムHTTPSサーバーの信頼評価を行った後、次の2つの方法のいずれかで課題を解決する必要があります。

接続を拒否する場合は、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:@"domaintoverride.com"])
        {
            NSURLCredential *credential = 
                          [NSURLCredential credentialForTrust:
                                          challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

bothの両方を処理する必要があることに注意してくださいその他のすべての場合。 「他のすべてのケース」の部分を処理しない場合、動作結果は未定義です。

26
memmons

新しい証明書の90日間無料トライアルを提供している信頼できるSSL認証局をオンラインで見つけてください。サーバーに証明書をインストールします。これで、証明書を「更新」するためにお金を払う価値があるかどうかを判断できるようになるまで、アプリを開発する90日間があります。自己署名証明書を使用するという私の決定は経済的に動機付けられており、90日でSSL証明書にお金を費やす価値があるかどうかを判断できるようにアプリを開発するのに十分な時間を与えてくれるので、これは私にとって最良の答えです。このアプローチにより、自己署名証明書を受け入れるように調整されたコードベースを実行することのセキュリティ上の影響に対処する必要がなくなります。甘い!ブートストラップに賛成です!

12
John Erck

自分でhugeを支持し、しないでください。

論文を読むことから始めてください 世界で最も危険なコード:非ブラウザソフトウェアでSSL証明書を検証する 、特にセクション10 、「証明書検証の解除または無効化」。具体的には、Cocoa関連のブログを呼び出しています。このブログでは、求めていることを具体的に説明しています。

しかし、しないでください。 SSL証明書のチェックを無効にすることは、時限爆弾をアプリに導入するようなものです。いつか、いつか、誤って有効にされたままになり、ビルドがワイルドになります。その日、ユーザーは深刻なリスクにさらされます。

代わりに、特定のデバイスにインストールして信頼できる中間証明書で署名された証明書を使用する必要があります。これにより、自分以外のデバイスを危険にさらすことなく、SSL検証を成功させることができます。

11
Shaggy Frog

Friherdのソリューションと同じですが、Swiftで:

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);
    }
}
6
eric

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)
}
5
Lepidopteron

.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)
    }
}
1
yycking

ここに私のために働いた解決策があります。両方のメッセージを含む接続のデリゲートを介して接続を受け入れる必要があります。

- (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暗号化のみが重要ですが、ここでは署名機関が考慮していないため、セキュリティが低下する可能性があります。

0
Maxime T

これは私にとっては自己署名でうまく動作します:

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]);
}
0

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)
                }
            }

        }
    }
0
luhuiya

おそらくより良い方法は、アクセスされているサービスに対してURLが正確であることを(視覚的に)確認する証明書を受け入れる機会をユーザーに提供することです。たとえば、ホストが何らかのアプリ設定に入力されている場合、ユーザーのエントリでテストし、ユーザーがその場で決定できるようにします。

この「ユーザー確認」戦術がSafariによって使用されていること、つまりAppleによって容認されていることを考慮してください。

NSErrorRecoveryAttemptingを掘り下げることをお勧めします(自分でやっていない) http://Apple.co/22Au1GR

ホストを確認してから、ここに記載されている個々のURL除外ルートを使用します。実装によっては、将来の参照のために除外としてホストを保存することも意味があります。

これは、Appleが本質的にCocoaに実装したもののように思えますが、現時点では、「簡単なボタン」は見つかりませんでした。 NSURLまたはNSURLSessionの何かに「kLetUserDecide」フラグがあれば、誰もがデリゲートメソッドとNSErrorRecoveryAttemptingプロトコルを実装する必要がなくなります。

0
William Cerniuk