要約
SecTrust
を使って、SSLエラーへの対処を行います。
概要
Safariでブラウジングしていると、サイトによって「接続はプライベートはありません」と表示される場合があります。
これに対し、「詳細を表示」→「この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
でその接続をキャンセルします。
アクセス時の画像
自己証明書
期限切れ
revoked
まとめ
つかおう!SecTrust!