ここ最近、Java のセキュリティ名目での やんちゃ ぶりには、振り回されている人も多いのではないだろうか。私も、Java 7 Update 40 以降でアプレット署名に自己証明書が使えなくなり、その対応に煩わされた。
クライアント PC に証明書をインポートする作業は、keytool で自動化できる。自己証明書の時はそれで問題なかったのだが、正規の証明書に変えた途端、上手くいかなくなった。具体的には、証明書をインポートしてもアプレット実行時に出てくるダイアログを抑止できない。
証明書の作り方に問題があるのかも知れないが、生憎とそこは別の人の仕事。まずは渡された証明書で何とかする方法を探してみる。(恐らくは証明書の Common Name が怪しいと睨んでいるが、未検証)
私を混乱させたのは下記の挙動。
- Java コントロールパネルから証明書をインポートしても、アプレット実行時にダイアログが出てきてしまう。
- ダイアログで「次回から表示しない」を選択して実行すると、ダイアログは二度と出てこない。
- (1)と(2)についてそれぞれ証明書をエクスポートすると、両者は完全に一致する。
つまりエクスポートした証明書は全く同じなのに、ダイアログが出る or 出ない、という違いが出る。インポート方法を色々と試してみたが、結局(2)以外でダイアログを抑止することはできなかった。最後に、ダメ元で気になっていたことを試してみた。実は(1)と(2)では、1 つだけ違いがある。それがキーストア中の「別名」(alias)表示だ。これも keytool で見ることができる。
Java コントロールパネルから証明書をインポートした場合:
別名: deploymentusercertnullnullnulljava.util.random@1a36121
ダイアログで「次回から表示しない」を選択した場合:
別名: deploymentusercert$tsflag$loc=http//example.com:80##docbase:http//example.com:80java.util.random@10acb9b
後者に、怪しげな呪文が見て取れる。ダメ元で後者の「別名」でインポートしてみると・・・、
やったよ、ビンゴ! :-D
もうね、何でこんな仕様なのかと。確かに前述のダイアログには「上記の発行者と場所」とあったが、まさか「場所」をこんな方法で覚えるとか難度が高過ぎだろう。
色々と試した結果、「別名」は次の形式が必要なようだ。(Java 7 Update 51 にて確認)
- $tsflag$loc=http//example.com:80##docbase:http//example.com:80
- 「http//example.com:80」は適宜変更。ポートは省略不可。SSL なら「https//example.com:443」とする。
- パースに問題なさそうな文字列であれば、先頭・末尾に追加可能。途中に入れることも可能だが、やらない方が無難だと思う。
以上を踏まえて、証明書インポート BAT の例。
importcert.bat:
@echo off
setlocal
set SITE_URL=http//example.com:80
set CERT_FILE=%~dp0mycert.cer
set KS_EMPTY_FILE=%~dp0trusted.certs.empty
set KS_ALIAS=deploymentusercert$tsflag$loc=%SITE_URL%##docbase:%SITE_URL%
set JAVA_VERSION=7
set JAVA_HOME=%ProgramFiles(x86)%\Java\jre%JAVA_VERSION%
if not exist "%JAVA_HOME%" (
set JAVA_HOME=%ProgramFiles%\Java\jre%JAVA_VERSION%
)
set keytool=%JAVA_HOME%\bin\keytool.exe
set LocalLow=%USERPROFILE%\AppData\LocalLow
if not exist "%LocalLow%" (
set LocalLow=%AppData%
)
set ks_dir=%LocalLow%\Sun\Java\Deployment\security
set ks_file=%ks_dir%\trusted.certs
set result=1
if not exist "%keytool%" (
echo ERROR: not found: "%keytool%"
goto exit
)
if not exist "%CERT_FILE%" (
echo ERROR: not found: "%CERT_FILE%"
goto exit
)
if not exist "%ks_file%" (
mkdir "%ks_dir%" 2>NUL
copy /v "%KS_EMPTY_FILE%" "%ks_file%"
if not exist "%ks_file%" (
echo ERROR: not found: "%ks_file%"
goto exit
)
)
"%keytool%" -list ^
-alias "%KS_ALIAS%" ^
-keystore "%ks_file%" ^
-storepass "" >NUL
if not errorlevel 1 (
echo already imported.
goto success
)
"%keytool%" -importcert -v ^
-alias "%KS_ALIAS%" ^
-file "%CERT_FILE%" ^
-keystore "%ks_file%" ^
-storepass "" ^
-noprompt
set result=%ERRORLEVEL%
if %result% neq 0 (
echo ERROR: keytool.exe: code=%result%
goto exit
)
:success
set result=0
:exit
if %result% equ 0 (
echo OK
) else (
echo Failed
)
pause
exit /b %result%
実行には上記 BAT に加え、下記ファイルが必要。
- mycert.cer (インポートする証明書)
- trusted.certs.empty (空のキーストアファイル)
詳細は BAT を解読して貰うとして、ファイル「trusted.certs.empty」については説明が必要だと思う。
keytool は、キーストアファイルが存在しなければ新たにファイルを作成する。しかしこの時、何故かパスワードを強要してくる(-storepass "" は効かない)ため、パスワードなしキーストアを作ることができない。一方 Java コントロールパネルから証明書をインポートすると、パスワードなしキーストアが作成される。つまり、Java コントロールパネルはパスワードなしキーストアを使うくせに、keytool からはそれを作ることができないのだ。もうほんと、この仕様を作った奴はタヒねと言いたい。
trusted.certs.empty は、予め作成した空のパスワードなしキーストアだ。Java コントロールパネルで適当な証明書をインポート → 削除すれば作成できる(ファイル場所は %ks_file% を参照)。私が確認した限り、Windows XP と Windows 8 とでファイルは完全に一致したので、今後の互換性も問題ないだろう。
下記は、インポート済み証明書を一覧表示する BAT の例。「別名」もこれで確認できる。
listcerts.bat:
@echo off
setlocal
set JAVA_VERSION=7
set JAVA_HOME=%ProgramFiles(x86)%\Java\jre%JAVA_VERSION%
if not exist "%JAVA_HOME%" (
set JAVA_HOME=%ProgramFiles%\Java\jre%JAVA_VERSION%
)
set keytool=%JAVA_HOME%\bin\keytool.exe
set LocalLow=%USERPROFILE%\AppData\LocalLow
if not exist "%LocalLow%" (
set LocalLow=%AppData%
)
set ks_dir=%LocalLow%\Sun\Java\Deployment\security
set ks_file=%ks_dir%\trusted.certs
set result=1
if not exist "%keytool%" (
echo ERROR: not found: "%keytool%"
goto exit
)
if not exist "%ks_file%" (
echo ERROR: not found: "%ks_file%"
goto exit
)
"%keytool%" -list -v ^
-keystore "%ks_file%" ^
-storepass ""
set result=%ERRORLEVEL%
if %result% neq 0 (
echo ERROR: keytool.exe: code=%result%
)
:exit
pause
exit /b %result%
今回はこれで解決だが、問題は、今後の互換性は保証されない ということ。
事実、Java 7 Update 45 → 51 で「別名」の形式が変わった。具体的には、u51 で「##docbase:...」が増えた。このせいで、u45 で証明書をインポートしていても、u51 にアップデートすると再びダイアログが出てきてしまう。ここまで来ると、分かってて嫌がらせをしているとしか思えない。(u51 アップデート時に出てくるダイアログで「セキュリティ・プロンプトの復元」に初期でチェックが入っているのも、これを隠すための陰謀だと思っている)
そろそろ、世界中の IT エンジニアは Java の横暴に NO! って言っても良い頃だと思う。
参考:
2014-04-28 追記
Java 7 Update 55 にて、更にマジックワード「##from」が増えた模様。
- $tsflag$loc=http//example.com:80##docbase:http//example.com:80##from:http//example.com:80