[iOS 9] App Transport Security の挙動を調べる
はじめに
iOS 9でApp Transport Security(以降、ATS)というセキュリティ機能が実装されました。 概要については[iOS 9] iOS 9 で追加された App Transport Security の概要をご覧ください。
ざっくり言うと、
- httpでは通信できない
- httpsでもATSのセキュリティ要件を満たしていないとエラーになる
ということなので今回はiOS SDKでhttp(s)通信を行う各コンポーネントについて実際の挙動はどうなのか調べてみました。
- NSURLConnection
- NSURLSession
- UIWebView
- WKWebView
- SFSafariViewController
接続先
接続先については下記パターンとし、それぞれについて上記コンポーネントを試します。
- https://dev.classmethod.jp/→httpのページ
- https://www.facebook.com→httpsだが、ATSのセキュリティ要件を満たしていない(SHA-1)
- https://www.google.com→httpsでATSのセキュリティ要件を満たす(SHA-256)
検証環境
- Xcode 7.0.1
- iPhone 6s シミュレータ
NSURLConnection
まずはiOS 9でdeprecatedになりましたが、NSURLConnectionで試してみます。
HTTP
httpのページにリクエストしてみましょう。 下記のようなコードで接続先だけ変えていきます。
let url = NSURL(string: "https://dev.classmethod.jp/") let request = NSURLRequest(URL: url!) NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (res: NSURLResponse?, data: NSData?, error: NSError?) -> Void in if error != nil { print("Failed.") print(error) } else { print("succeeded.") } }
結果
下記の通りエラーとなりました。
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file. Failed. Optional(Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSUnderlyingError=0x7f8101dd9220 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSErrorFailingURLStringKey=https://dev.classmethod.jp/, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection., NSErrorFailingURLKey=https://dev.classmethod.jp/}}, NSErrorFailingURLStringKey=https://dev.classmethod.jp/, NSErrorFailingURLKey=https://dev.classmethod.jp/, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.})
HTTPS(SHA-1)
let url = NSURL(string: "https://www.facebook.com/") ・・・以下省略
結果
下記の通りエラーとなりました。
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802) Failed. Optional(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey={type = immutable, count = 2, values = ( 0 : <cert(0x7f8862eb1990) s: *.facebook.com i: DigiCert High Assurance CA-3> 1 : <cert(0x7f8862eb1fa0) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA> )}, NSUnderlyingError=0x7f8862eb5030 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://www.facebook.com/, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates={type = immutable, count = 2, values = ( 0 : <cert(0x7f8862eb1990) s: *.facebook.com i: DigiCert High Assurance CA-3> 1 : <cert(0x7f8862eb1fa0) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA> )}, _kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., _kCFStreamErrorDomainKey=3, NSErrorFailingURLKey=https://www.facebook.com/, _kCFStreamErrorCodeKey=-9802}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://www.facebook.com/, NSErrorFailingURLStringKey=https://www.facebook.com/, NSErrorClientCertificateStateKey=0})
HTTPS(SHA-256)
let url = NSURL(string: "https://www.google.com") ・・・以下省略
結果
成功しました!
succeeded.
NSURLSession
続いてNSURLSessionで試してみます。
HTTP
let url = NSURL(string: "https://dev.classmethod.jp/") let session = NSURLSession.sharedSession() let request = NSURLRequest(URL: url!) let task = session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) -&gt; Void in if error != nil { print("Failed.") print(error) } else { print("succeeded.") } } task.resume()
結果
下記の通りエラーとなりました。
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file. Failed. Optional(Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSUnderlyingError=0x7fc3915242a0 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "(null)"}, NSErrorFailingURLStringKey=https://dev.classmethod.jp/, NSErrorFailingURLKey=https://dev.classmethod.jp/, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.})
HTTPS(SHA-1)
let url = NSURL(string: "https://www.facebook.com/") ・・・以下省略
結果
下記の通りエラーとなりました。
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802) Failed. Optional(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey={type = immutable, count = 2, values = ( 0 : <cert(0x7fa55047d190) s: *.facebook.com i: DigiCert High Assurance CA-3> 1 : <cert(0x7fa55047d770) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA> )}, NSUnderlyingError=0x7fa55070f4e0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates={type = immutable, count = 2, values = ( 0 : <cert(0x7fa55047d190) s: *.facebook.com i: DigiCert High Assurance CA-3> 1 : <cert(0x7fa55047d770) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA> )}}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://www.facebook.com/, NSErrorFailingURLStringKey=https://www.facebook.com/, NSErrorClientCertificateStateKey=0})
HTTPS(SHA-256)
let url = NSURL(string: "https://www.google.com") ・・・以下省略
結果
成功です!
succeeded.
UIWebView
続いてUIWebViewで試してみます。 Storyboard上でUIWebViewを貼り付けてページをロードしてみます。
HTTP
let url = NSURL(string: "https://dev.classmethod.jp/") self.webView.delegate = self let request = NSURLRequest(URL: url!) self.webView.loadRequest(request)
結果
UIWebViewDelegateのdidFailLoadWithErrorが呼ばれ、下記の通りエラーとなりました。 もちろんWebViewには何も表示されません。
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file. Optional(Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSUnderlyingError=0x7fcf40ea1890 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSErrorFailingURLStringKey=https://dev.classmethod.jp/, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection., NSErrorFailingURLKey=https://dev.classmethod.jp/}}, NSErrorFailingURLStringKey=https://dev.classmethod.jp/, NSErrorFailingURLKey=https://dev.classmethod.jp/, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.})
HTTPS(SHA-1)
let url = NSURL(string: "https://www.facebook.com/") ・・・以下省略
結果
こちらもUIWebViewDelegateのdidFailLoadWithErrorが呼ばれ、下記の通りエラーとなりました。
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802) Optional(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7fd9eb525aa0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://www.facebook.com/, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates={type = immutable, count = 2, values = ( 0 : <cert(0x7fd9eb4100f0) s: *.facebook.com i: DigiCert High Assurance CA-3> 1 : <cert(0x7fd9eb406010) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA> )}, _kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., _kCFStreamErrorDomainKey=3, NSErrorFailingURLKey=https://www.facebook.com/, _kCFStreamErrorCodeKey=-9802}}, NSErrorPeerCertificateChainKey={type = immutable, count = 2, values = ( 0 : <cert(0x7fd9eb4100f0) s: *.facebook.com i: DigiCert High Assurance CA-3> 1 : <cert(0x7fd9eb406010) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA> )}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://www.facebook.com/, NSErrorFailingURLStringKey=https://www.facebook.com/, NSErrorClientCertificateStateKey=0})
HTTPS(SHA-256)
let url = NSURL(string: "https://www.google.com") ・・・以下省略
結果
ページが表示されました!
WKWebView
続いてWKWebViewで試してみます。 コードでWKWebViewを生成してaddSubviewします。
HTTP
override func viewDidLoad() { super.viewDidLoad() let url = NSURL(string: "https://dev.classmethod.jp/") self.webView = WKWebView() self.webView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(self.webView) let top = NSLayoutConstraint(item: self.webView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.topLayoutGuide, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0) let left = NSLayoutConstraint(item: self.webView, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Left, multiplier: 1.0, constant: 0) let right = NSLayoutConstraint(item: self.webView, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Right, multiplier: 1.0, constant: 0) let bottom = NSLayoutConstraint(item: self.webView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.bottomLayoutGuide, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0) self.view.addConstraints([top, left, right, bottom]) self.webView.navigationDelegate = self let request = NSURLRequest(URL: url!) self.webView.loadRequest(request) }
結果
WKNavigationDelegateのdidFailProvisionalNavigation:が呼ばれ、下記の通りエラーとなりました。
Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={_WKRecoveryAttempterErrorKey=, NSErrorFailingURLStringKey=https://dev.classmethod.jp/, NSErrorFailingURLKey=https://dev.classmethod.jp/, NSUnderlyingError=0x7f7fb0d2d780 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSErrorFailingURLStringKey=https://dev.classmethod.jp/, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection., NSErrorFailingURLKey=https://dev.classmethod.jp/}}, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}
HTTPS(SHA-1)
override func viewDidLoad() { super.viewDidLoad() let url = NSURL(string: "https://www.facebook.com/") ・・・以下省略
結果
こちらもWKNavigationDelegateのdidFailProvisionalNavigation:が呼ばれ、下記の通りエラーとなりました。
Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSUnderlyingError=0x7fb911e18d60 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorDomainKey=3, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamPropertySSLClientCertificateState=0, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://www.facebook.com/, NSErrorFailingURLStringKey=https://www.facebook.com/, _kCFStreamErrorCodeKey=-9802}}, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSErrorPeerCertificateChainKey={type = mutable-small, count = 2, values = ( 0 : <cert(0x7fb911e1ff60) s: *.facebook.com i: DigiCert High Assurance CA-3> 1 : <cert(0x7fb911e21380) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA> )}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., _WKRecoveryAttempterErrorKey=, NSErrorFailingURLKey=https://www.facebook.com/, NSErrorClientCertificateStateKey=0, NSErrorFailingURLStringKey=https://www.facebook.com/}
HTTPS(SHA-256)
override func viewDidLoad() { super.viewDidLoad() let url = NSURL(string: "https://www.google.com") ・・・以下省略
結果
ページが表示されました!
SFSafariViewController
最後はSFSafariViewControllerで試してみます。
HTTP
@IBAction func userDidTapSFSafariViewControllerButton(sender: AnyObject) { let url = NSURL(string: "https://dev.classmethod.jp/") let vc = SFSafariViewController(URL: url!) vc.delegate = self self.presentViewController(vc, animated: true, completion: nil) } func safariViewController(controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) { print(didLoadSuccessfully) }
結果
なんと表示されました!
HTTPS(SHA-1)
@IBAction func userDidTapSFSafariViewControllerButton(sender: AnyObject) { let url = NSURL(string: "https://www.facebook.com/") ・・・以下省略
結果
こちらも表示されました!
HTTPS(SHA-256)
@IBAction func userDidTapSFSafariViewControllerButton(sender: AnyObject) { let url = NSURL(string: "https://www.google.com") ・・・以下省略
結果
表示されました!
まとめ
今回試した結果をまとめると下記の通りです。
http | http(SHA-1) | https(SHA-256) | |
NRURLConnection | ✖️ | ✖️ | ◯ |
NSURLSession | ✖️ | ✖️ | ◯ |
UIWebView | ✖️ | ✖️ | ◯ |
WKWebView | ✖️ | ✖️ | ◯ |
SFSafariViewController | ◯ | ◯ | ◯ |
SFSafariViewControlleだけはhttpやATSの要件を満たしていないページでも表示することができましたが、 それ以外のコンポーネントについてはATSの要件を満たすサーバー証明書を用意したり、iOSアプリ側でATSを無効にしたり特定のドメインだけを除外するような設定を しなければいけないようです。
今回は以上です。