もともと、ローカルでのHTTPS接続関連の開発やテストはオレオレ証明書で済ませていた。
けれど、社内の認証局から発行された証明書を使う場合に失敗したので、その辺の話を。きっとまた同じ失敗を繰り返すので、自分のためにまとめておく。
[SSLサーバ証明書を秘密鍵をインポートする]
社内認証局から送られてきたのは以下の2点。
これらのファイルをJavaのキーストア(cacerts)にkeytoolを利用して取り込み、自分の端末でサーバを動かして、チームメンバーにHTTPS接続させたい。
というわけで以下の手順を実行する。
- opensslを利用して証明書を秘密鍵をpkcs12形式のファイルに変換する
- keytoolでpkcs12形式のファイルをcacertsにimportkeystoreオプションでimportする
- Spring BootアプリのVM引数を以下のように設定しアプリを起動する
1.opensslを利用して証明書を秘密鍵をpkcs12形式のファイルに変換する
sudo openssl pkcs12 -export -in cert.pem -inkey key.pem \n -out temp.p12 -name mycert
実行すると、パスワードが要求されるので、適当なパスワードを2回入力する。あとでインポートするときに同じパスワードを要求されるので覚えておく必要がある。
cert.pemとkey.pemは実際に利用するファイルの名前やファイルパスに合わせる必要がある。
temp.p12は一時的なファイルなので、適当な名前にした。今回は既存のキーストアであるcacertsにcert.pemとkey.pemを移行するのが目的なので適当な名前で良い。もちろん、cacertsを使わずに、PKCS12形式のファイルをキーストアとして利用することもできる。
2.keytoolでpkcs12形式のファイルをcacertsにimportkeystoreオプションでimportする
sudo keytool -importkeystore
-srckeystore temp.p12 -srcstoretype PKCS12 -srcstorepass mypassword
-alias mycert
-deststorepass changeit -destkeypass changeit -destkeystore cacerts
-srckeystoreには手順1で作成したファイルのパスを指定する。同じく、-srcstorepassには手順1で入力したパスワードを指定する。
-aliasは手順1と同じでなくても良いが次の手順で利用するのであまり変な名前にしないように。
3.Spring BootアプリのVM引数を以下のように設定しアプリを起動する
-Dserver.port=8443 -Dserver.ssl.keyStore=/path/to/cacerts -Dserver.ssl.keyStorePassword=changeit -Dserver.ssl.keyStoreType=jks -Dserver.ssl.keyAlias=mycert
ポートは他と被らなければなんでも良い。keyAliasは手順2で指定したエイリアスを指定する。
手順2を飛ばしてPKCS12形式のファイルを利用することもできる。その場合はkeyStoreのパスを先ほどのtemp.p12のパスを指定し、keyStoreTypeにPKCS12を指定する。
ちなみにVM引数はキャメルケース(keyStoreType)でもスネークケース(key-store-type)でもどちらでも利用できる。
[詰まったポイント]
一発で正解の手順を出せたわけではない。ルート証明書をキーストアに取り込んだ経験はあったので、同じように証明書を直接importすれば済むと思っていた。
sudo keytool -import -file cert.pem -keystore cacerts -storepass changeit -alias mycert
が、Spring Bootでサーバを起動するとjava.io.IOException: Alias name [mycert] does not identify a key entry
のエラーが出てサーバが起動しない。よく考えたら証明書の公開鍵だけあっても、秘密鍵がないとHTTPS接続はできない。
ちなみに詳細なスタックトレースは以下の通り。
org.apache.catalina.LifecycleException: Failed to start component [Connector[HTTP/1.1-8443]] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.core.StandardService.addConnector(StandardService.java:225) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.addPreviouslyRemovedConnectors(TomcatWebServer.java:256) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:198) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.startWebServer(ServletWebServerApplicationContext.java:300) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:162) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:553) [spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at com.kdnakt.spring.bean_sandbox.MySpringApp.main(MySpringApp.java:14) [classes/:na] Caused by: org.apache.catalina.LifecycleException: Protocol handler start failed at org.apache.catalina.connector.Connector.startInternal(Connector.java:1020) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) ~[tomcat-embed-core-8.5.31.jar:8.5.31] ... 13 common frames omitted Caused by: java.lang.IllegalArgumentException: Alias name [mycert] does not identify a key entry at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:116) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:87) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:225) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.tomcat.util.net.AbstractEndpoint.start(AbstractEndpoint.java:1150) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:591) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.catalina.connector.Connector.startInternal(Connector.java:1018) ~[tomcat-embed-core-8.5.31.jar:8.5.31] ... 14 common frames omitted Caused by: java.io.IOException: Alias name [mycert] does not identify a key entry at org.apache.tomcat.util.net.jsse.JSSEUtil.getKeyManagers(JSSEUtil.java:229) ~[tomcat-embed-core-8.5.31.jar:8.5.31] at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:114) ~[tomcat-embed-core-8.5.31.jar:8.5.31] ... 19 common frames omitted
[オレオレ証明書(自己署名証明書)の場合]
IPアドレスが変わった場合などに、毎回証明書を配布する手間があるが、それでもよければオレオレ証明書を利用することもできる。
sudo keytool -genkeypair -keyalg RSA -keysize 2048 -alias mycert -keystore cacerts -storepass changeit
コマンドを入力すると証明書の情報を入力するよう求められる。
[まとめ]
- keytoolで直接証明書と秘密鍵をインポートすることはできない
- OpenSSLを利用してPKCS12形式のファイルに変換したものならインポートできる
ちなみに本文中ではsudoを利用したが、Windowsの場合は、コマンドプロンプトを管理者権限で開いておく必要があるので注意が必要。