この記事は公開されてから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
接続先
接続先については下記パターンとし、それぞれについて上記コンポーネントを試します。
- 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を無効にしたり特定のドメインだけを除外するような設定を しなければいけないようです。
今回は以上です。