12
9

More than 3 years have passed since last update.

URLSessionでSSLエラーが起きた時にSafariと同様にアクセスできるようにする

Last updated at Posted at 2020-08-31

要約

SecTrust を使って、SSLエラーへの対処を行います。

概要

Safariでブラウジングしていると、サイトによって「接続はプライベートはありません」と表示される場合があります。

接続はプライベートはありません 2020-08-31 22.58.23.png
これに対し、「詳細を表示」→「このWebサイトを閲覧」とすることにより、ユーザーの責任でサイトを閲覧できます。
これをURLSessionでも行いたいと思います。

Info.plistの設定

Info.plistにApp Transport Security Settingsの設定をする必要があります。
Exception Domains → 目的のドメイン → NSExceptionAllowsInsecureHTTPLoads をYESに設定します。
今回の例では、badssl.comのサブドメインでもテストしたいので、NSIncludesSubdomainsもYESにしておきます。

    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>badssl.com</key>
            <dict>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSIncludesSubdomains</key>
                <true/>
            </dict>
        </dict>
    </dict>

URLSessionへの実装

まず、URLSessionはdelegateをつけられるように初期化します。
そして、URLSessionTaskDelegate など、目的のデリゲートを設定して、urlSession(_:didReceive:completionHandler:)などでハンドリングしてあげます。

コード

class ViewController: UIViewController {
    lazy var session: URLSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)

    override func viewDidLoad() {
        super.viewDidLoad()
        let task = session.dataTask(with: URL(string: "https://revoked.badssl.com")!)
        task.resume()
    }
}

extension ViewController: URLSessionTaskDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        print(#function,#line,"\(String(describing: error))")
    }

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        //サーバー信頼チェック以外はデフォルトの挙動を行う
        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
            let serverTrust = challenge.protectionSpace.serverTrust else {
                completionHandler(.performDefaultHandling, nil)
                return
        }

        // Trustを確認する
        SecTrustEvaluateAsyncWithError(serverTrust, DispatchQueue.global()) { (secTrust, trusted, error) in
            if trusted {
                completionHandler(.performDefaultHandling, nil) // 信頼できる場合デフォルトの挙動を行う
            } else {
                // エラーメッセージを表示
                let title:String
                if let error = error {
                    title = error.localizedDescription
                } else {
                    title = "接続はプライベートではありません"
                }
                DispatchQueue.main.async {
                    //アラートを表示して、ユーザーに選択を促す
                    let alert = UIAlertController(title: title,
                                                  message: "それでもサーバに接続しますか?",
                                                  preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "接続する", style: .destructive, handler: { (alertAction) in
                        completionHandler(.useCredential, URLCredential(trust: secTrust))
                    }))

                    alert.addAction(UIAlertAction(title: "接続しない", style: .cancel, handler: { (alertAction) in
                        completionHandler(.cancelAuthenticationChallenge, nil)
                    }))

                    self.present(alert, animated: true, completion: nil)
                }
            }
        }
    }
}

コード説明

urlSession(_:didReceive:completionHandler:)はベーシック認証時などもコールされるのですが、今回はサーバーの信頼に対する処理だけなので、NSURLAuthenticationMethodServerTrustで確認します。
それ以外の場合は特に対処の必要は今回は考慮しないのでリターンをしますが、completionHandlerを必ずコールする必要があるので、.performDefaultHandling をコールしておきます。

まず、SecTrustEvaluateAsyncWithError にsecTrustを渡して信頼できるかどうかをコールバックでもらいます。
コールバックでは信頼結果が返ってきて、信頼できると判断できる場合はそのまま.performDefaultHandlingで処理継続できますが、信頼できないとなった場合、アラートを表示してユーザーに判断をしてもらいます。
CFErrorのlocalizedDescriptionにはエラーの原因が入っているので、それをユーザーに提示し、ユーザーが接続可能と判断した場合は.useCredentialで処理継続できます。
ユーザーが接続しないと判断した場合は.cancelAuthenticationChallengeでその接続をキャンセルします。

アクセス時の画像

自己証明書

自己証明書.png

期限切れ

期限切れ.png

revoked

revoked.png

まとめ

つかおう!SecTrust!

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9