Help us understand the problem. What is going on with this article?

Android端末にインストールされた認証局でサーバ証明書を検証する

More than 1 year has passed since last update.

目的

タイトル通りだが、方法を見つけるのに苦労したので記載しておく。
これを実装すると、自前のRoot認証局が署名したサーバ証明書でも検証してくれる。
自前のRoot認証局のインストール方法は、「設定」>「セキュリティ」>「SDカードからインストール」をタップし、証明書(PEM形式でよい)を選択する。インストールに成功すると、「設定」>「セキュリティ」>「信頼できる認証情報」の「ユーザー」タブに選択した証明書が表示される。

実装

fun connect() {
    // 端末にインストールされている「信頼できる認証情報」ストレージを取得する
    val caStore = KeyStore.getInstance("AndroidCAStore").apply {
        load(null)
    }
    // デバッグ用: 「信頼できる認証情報」をログ出力する
    caStore.aliases().toList().forEach { alias ->
        Log.d("----", "alias=${alias}, certificate=${caStore.getCertificate(alias)}")
    }
    // SSLハンドシェイク時に受信したサーバ証明書の署名者を、検証してくれるTrustManagerを生成するオブジェクトを作成する
    val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
        init(caStore)
    }
    // 上で作成したTrustManagerを使う SSLContext を作成する
    val context: SSLContext = SSLContext.getInstance("TLS").apply {
        init(null, trustManagerFactory.trustManagers, null)
    }
    // HTTPS 通信を行う
    val url = URL("https://example.com/index.html")
    val urlConnection = (url.openConnection() as HttpsURLConnection).apply {
        sslSocketFactory = context.socketFactory
    }
    // デバッグ用: 通信結果をログ出力する
    val inputStream = urlConnection.inputStream
    Log.d("----", InputStreamReader(inputStream).readText())
}

注意点

端末にRoot認証局をインストールし、サーバ証明書を中間認証局(Root認証局で署名)で署名しているにもかかわらず、検証に失敗する場合がある。その時は、下記のように自前のX509TrustManagerを実装し、サーバから受信する証明書チェーンを確認する。

fun connect() {
    val context: SSLContext = SSLContext.getInstance("TLS").apply {
        //        init(null, trustManagerFactory.trustManagers, null)
        init(
            null,
            arrayOf(object : X509TrustManager {
                override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
                }

                override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
                    // デバッグ用: サーバから受信した証明書チェーンをログ出力する
                    Log.d("----", "chain=${Arrays.toString(chain)}")
                }

                override fun getAcceptedIssuers(): Array<X509Certificate> {
                    return emptyArray()
                }
            }),
            null
        )
    }
    // HTTPS 通信を行う
    val url = URL("https://example.com/index.html")
    val urlConnection = (url.openConnection() as HttpsURLConnection).apply {
        sslSocketFactory = context.socketFactory
    }
    // デバッグ用: 通信結果をログ出力する
    val inputStream = urlConnection.inputStream
    Log.d("----", InputStreamReader(inputStream).readText())
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away