JavaにAmazonRDSのルートCAを許可させる

TLS上でAWSのDBインスタンスに接続する場合に証明書が必要になる。 そして場合によっては中間証明書も必要になる

AWS外からアクセスするために証明書をインストールしたので方法をメモする。 少しだけJava暗号化アーキテクチャ(JCA)でTLSをどのように信頼するかについて整理した。

tl;dr

Java 全体で利用されるキーストアに登録した。実現するには下のコマンドを叩けばいい。 -import を使う例が多いけど -importcert が標準になっている。 -alias は任意につける。

12
wget https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem
sudo keytool -importcert -trustcacerts -file ./rds-combined-ca-bundle.pem -keystore /usr/lib/jvm/java-8-oracle/jre/lib/security/cacerts -alias rds-ca-combined

今回の目的は Java で書いていたコードから RDS で構築した Postgresql に接続することだったので、 postgresql のURIで sslrootcert パラメタを明示的に指定する方法もあった。

たとえば下のようになる。

1
postgresql://${user}:${pass}@{hostport}/${database}?sslcert=${path_to_cert}&sslmode=require

Apache Beamユーザーなので一応、上記のURIに対応するコードもメモする。 org.apache.beam.sdk.io.jdbc.JdbcIODataSourceConfigurationKotlin で下のように作れる。

123456
val conf = JdbcIO.DataSourceConfiguration.create(
                "org.postgresql.Driver",
                "jdbc:postgresql://$hostport/$database")
                .withUsername(user)
                .withPassword(password)
                .withConnectionProperties("ssl=true;sslcert=$pathToCert")

Javaはデフォルトで順に下記のパスを探索して存在するファイルをトラストマネージャに利用する。

  1. $JAVA_HOME/lib/security/jssecacerts
  2. $JAVA_HOME/lib/security/cacerts

ref.

周辺の整理

URIで直接指定する方法はともかくとして、Javaで証明書を許可する方法には説明が足りないので調べたことをメモする。

証明書の格納場所

Javaのセキュリティ周辺コンポーネントはJCAと呼ばれるアーキテクチャになっている

JCAではセキュリティに関わる各機能のインタフェース(In/Outの仕様)が決められており、利用者は対象インタフェースのファクトリを使い実装クラスのインスタンス化を行う。 これにより対応アルゴリズムなどの提供範囲の拡張がしやすくなっている。

例えば SHA256 を利用する場合。

1
md = MessageDigest.getInstance("SHA-256");

インタフェースとして定義された暗号サービスをエンジンと呼び。 インスタンス化される具体的な実装部分をプロバイダーと呼ぶ。

そして、TLSの証明書が信頼されるかは TrustManager エンジンが関わっていて、提供してるプロバイダーは SunJSSE プロバイダーなどになる。

SunJSSEのデフォルトのトラストマネージャは lib/security/cacerts となるのでここにルート証明書を追加すればいい。

  1. javax.net.ssl.trustStoreプロパティが定義されている場合、TrustManagerFactoryは、このシステム・プロパティで指定されたファイル名を使用してファイルを検索し、KeyStoreパラメータにそのファイルを使用しようとします。javax.net.ssl.trustStorePasswordシステム・プロパティも定義されている場合は、ファイルを開く前に、その値を使用してトラストストアのデータの整合性をチェックします。

  2. javax.net.ssl.trustStoreプロパティが定義されているが、指定したファイルが存在しない場合、空のキーストアを使用するデフォルトのTrustManagerが作成されます。 javax.net.ssl.trustStoreシステム・プロパティが指定されていない場合: a. java-home/lib/security/jssecacertsファイルが存在すれば、そのファイルが使用されます。 b. java-home/lib/security/cacertsファイルが存在すれば、そのファイルが使用されます。 c. これらのどちらのファイルも存在しない場合、SSL暗号化方式群が匿名であり、認証を行わないので、トラストストアは必要ありません。

またJava10でも同じだった。

JDKおよびJREのファイル構造(jdk8)によると lib/security/ は名前の通りセキュリティ管理に使用されるファイルの格納場所なので極めて自然。

証明書のリポジトリの管理

さっきのパスへkeytoolで格納する。 keytoolはJDKについてくる。証明書を扱うのでOpenSSLと似た機能もある。 比較している人がいた。

123456
$ echo $JAVA_HOME
/usr/lib/jvm/java-8-oracle/
$ sudo keytool -importcert -trustcacerts \
  -file ./rds-combined-ca-bundle.pem \
  -keystore /usr/lib/jvm/java-8-oracle/jre/lib/security/cacerts \
  -alias rds-ca-combined
comments powered by Disqus