[iOS 9] App Transport Security の挙動を調べる

2015.10.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

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

接続先

接続先については下記パターンとし、それぞれについて上記コンポーネントを試します。

検証環境

  • 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?) -&amp;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")
        ・・・以下省略

結果

ページが表示されました!

UIWebView_google

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")
        ・・・以下省略

結果

ページが表示されました!

WKWebView_google

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

結果

なんと表示されました!

sfsafariviewcontroller_devio

HTTPS(SHA-1)

    @IBAction func userDidTapSFSafariViewControllerButton(sender: AnyObject) {
        let url = NSURL(string: "https://www.facebook.com/")
        ・・・以下省略

結果

こちらも表示されました!

sfsafariviewcontroller_facebook

HTTPS(SHA-256)

    @IBAction func userDidTapSFSafariViewControllerButton(sender: AnyObject) {
        let url = NSURL(string: "https://www.google.com")
        ・・・以下省略

結果

表示されました!

sfsafariviewcontroller_google

まとめ

今回試した結果をまとめると下記の通りです。

http http(SHA-1) https(SHA-256)
NRURLConnection ✖️ ✖️
NSURLSession ✖️ ✖️
UIWebView ✖️ ✖️
WKWebView ✖️ ✖️
SFSafariViewController

SFSafariViewControlleだけはhttpやATSの要件を満たしていないページでも表示することができましたが、 それ以外のコンポーネントについてはATSの要件を満たすサーバー証明書を用意したり、iOSアプリ側でATSを無効にしたり特定のドメインだけを除外するような設定を しなければいけないようです。

今回は以上です。