目的
タイトル通りだが、方法を見つけるのに苦労したので記載しておく。
これを実装すると、自前の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())
}