2019年11月26日火曜日

[swift4] クライアント証明書が埋めこまれたiOSアプリだけに、httpsリクエストを許す


自分で立てたサーバーに、信頼できるiOSアプリだけにアクセスさせたい。
こんな時にどうするか。

登場する技術要素としては、
1. HTTPS
2. サーバー証明書
3. クライアント認証
があるわけですが、

1.は言わずとしれた暗号化になります。
これは他人の通信を垣間見ることはできなくなる、というだけの話であり、HTTPSで暗号化したから「とにかく大丈夫」という話にはなりません。

よ〜く考えていただきたいのですが、暗号化されたのは電話で言うところの「音声」だけであり、盗聴防止にはなっていますが、そこの電話番号にガンガンいたずら電話をかけることはできるのです。
無言電話を何万回もかけるいたずらもできるし、オレオレ詐欺みたいに家庭の事情をいろいろ聞き出す電話もできます。

電話がかかってくるのは仕方がないが、知らない電話番号には出ない方法はあって、これが3.になります。
クライアント証明書をもっていないiOSアプリからのアクセスについては、サーバーは拒否することができます。

ここではすでにサーバー側が仕上がっているとして、サーバーから払い出したクライアント証明書をiOSアプリに仕込む方法を記述します。

@yosshi_0511さんの記事を参考にさせていただきました。
https://qiita.com/yosshi_0511/items/b0a584166f9ab0dbb94f



ここではエッセンスだけをswift 4で示します。

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
    switch (challenge.protectionSpace.authenticationMethod) {
    case NSURLAuthenticationMethodServerTrust:
        if let trust = challenge.protectionSpace.serverTrust {
            let credential = URLCredential(trust: trust)
            completionHandler(.useCredential, credential)
            print("Serverはなんでも信じる")
            return
        }
    case NSURLAuthenticationMethodClientCertificate:
        if let path = Bundle.main.path(forResource: "ファイル名", ofType: "pfx") {
            if let data = NSData(contentsOf: URL(fileURLWithPath: path)) {
                var items: CFArray?
                let options = [kSecImportExportPassphrase: "パスコード"]
                if SecPKCS12Import(data, options as CFDictionary, &items) == errSecSuccess {
                    if let cfarr = items {
                        if let certEntry = (cfarr as Array).first as? [String: AnyObject] {
                            let identityRef = certEntry[kSecImportItemIdentity as String] as! SecIdentity
                            let credential = URLCredential(identity: identityRef, certificates: nil, persistence: .none)
                            completionHandler(.useCredential, credential)
                            print("Client認証成功")
                            return
                        }
                    }
                }
            }
        }
    default:
        print("その他")
    }
    completionHandler(.cancelAuthenticationChallenge, nil)
    print("キャンセル")
}

これで主題は達成できるのですが、「Serverはなんでも信じる」が気になると思います。
これが冒頭の2.の話です。
あやしいサーバーは排除したい場合は、ここでサーバー証明書のチェックを行います。
フィッシング詐欺サイト等、なりすましサーバーに対する対策となります。

が、自作のiOSアプリは、あらかじめ仕込んだサーバーだけにアクセスするはずなので、このチェックは「普通」は省略できるはずです。
むろん、アクセス先をユーザーが自由に書き換えることができる仕様なら、チェックは必要と思います。

0 件のコメント:

コメントを投稿