2014-02-24

Java: Import a certificate with `keytool'

ここ最近、Java のセキュリティ名目での やんちゃ ぶりには、振り回されている人も多いのではないだろうか。私も、Java 7 Update 40 以降でアプレット署名に自己証明書が使えなくなり、その対応に煩わされた。

クライアント PC に証明書をインポートする作業は、keytool で自動化できる。自己証明書の時はそれで問題なかったのだが、正規の証明書に変えた途端、上手くいかなくなった。具体的には、証明書をインポートしてもアプレット実行時に出てくるダイアログを抑止できない。

証明書の作り方に問題があるのかも知れないが、生憎とそこは別の人の仕事。まずは渡された証明書で何とかする方法を探してみる。(恐らくは証明書の Common Name が怪しいと睨んでいるが、未検証)

私を混乱させたのは下記の挙動。

  1. Java コントロールパネルから証明書をインポートしても、アプレット実行時にダイアログが出てきてしまう。
  2. ダイアログで「次回から表示しない」を選択して実行すると、ダイアログは二度と出てこない。
  3. (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

2014-02-01

Animes in the 4th quarter of 2013

アニメは IT エンジニアの必須科目です。 ということで簡単なレビューを。

前期からの視聴。光る素材は有ったのに、前期同様それを活かせずに終わってしまった。話が完全にキャラ負けした感じ。キャラデザは良かっただけに、話さえ良ければ化けたと思う。

前期からの視聴だが、相変わらずキャラは秀逸。ただ話は区切りの悪い終わり方と、(原作は知らないが)重要な章が端折られているように見えた。尤も前期の教訓で話の方は期待してはいなかったが、何故 12 話で作ったのか疑問。ただ、OP/ED は良い。

絵は綺麗だが、その他は平凡。エロゲ原作はこのパターンが多い気がする。まあエロゲだし、エロを取ったら(略)。結局、ジョルトのルールは最後まで良く分からなかった。

シリーズ通しての視聴。今回はごった煮らしいが、各話の繋がり(時系列)が良く分からなかった。個人的には臥煙伊豆湖がもっと出てきても良かった。あの手の頼れる存在は安心感を生む。金髪幼女は忍野メメの代わりにはならなかったと思う。あと、そろそろ超速字幕は何とかして欲しい。いちいち巻き戻して読むのが面倒。

リタイアせずに最後までは見られたが、何がやりたかったのか良く分からなかった。メカ? 家族愛? 時を越えた愛? いずれにせよ、特に感動もなく終わった。

  • 俺の脳内選択肢が、学園ラブコメを全力で邪魔している http://noucome.jp/

タイトルから食傷気味で、絵も全く好みでなく、脳内選択肢のバカ設定を知り、もう嫌な予感しかしなかったが意外に期待を裏切ってくれた。設定だけでなく主人公の周りも全てバカなのが良かった。これなら安心して続編を見られる(続くかは知らない)。