JavaでXML署名を実装した際にドン詰まりしたのでメモ。
##Javaアプリからkeytool -importkeystoreコマンドを実行
でもインポートされず。。。
実行時に関係するメソッドは以下の3つで、importProcessExec ⇒ getImportCommand ⇒ processExecの順で実行される。
sample.java
/**
* 証明書インポートコマンドを実行する.
*
* @param certFilePath 証明書ファイル
* @param destAlias 識別名
* @param keyStoreFilePath キーストアファイル
* @param certificatePass 証明書パスワード
* @param keyStorePass キーストアパスワード
* @return true: 正常終了, false: 異常終了
* @throws InterruptedException
*/
private boolean importProcessExec(File keyStoreFile, String destAlias, File certFile,
String certificatePass,
String keyStorePass) throws InterruptedException {
// 証明書のインポートを行うコマンド
List<String> command = getImportCommand(keyStoreFile, destAlias, certFile, certificatePass,
keyStorePass);
// インポートコマンド実行
return processExec(command);
}
/**
* 証明書インポートコマンドを取得する.
*
* @param certFilePath 証明書ファイル
* @param destAlias 識別名
* @param keyStoreFilePath キーストアファイル
* @param certificatePass 証明書パスワード
* @param keyStorePass キーストアパスワード
* @return resultList インポートコマンド
*/
private List<String> getImportCommand(File keyStoreFile, String destAlias, File certFile,
String certificatePass, String keyStorePass) {
// 証明書のインポートを行うコマンド
String command = "keytool -importkeystore -keystore %keyStoreFilePath% -srckeystore %certFilePath% -srcstoretype PKCS12 -srcalias 1 -destalias %destalias% -srcstorepass %certificatePass% -deststorepass %keyStorePass%";
// コマンドを文字列配列に分割
String[] commandList = command.split(" ");
List<String> resultList = new ArrayList<String>();
for (String cmd : commandList) {
switch (cmd) {
case "%keyStoreFilePath%":
cmd = cmd.replace("%keyStoreFilePath%", includeDoubleQuotes(keyStoreFile.getPath()));
break;
case "%certFilePath%":
cmd = cmd.replace("%certFilePath%", includeDoubleQuotes(certFile.getPath()));
break;
case "%destalias%":
cmd = cmd.replace("%destalias%", destAlias);
break;
case "%certificatePass%":
cmd = cmd.replace("%certificatePass%", certificatePass);
break;
case "%keyStorePass%":
cmd = cmd.replace("%keyStorePass%", keyStorePass);
break;
}
resultList.add(cmd);
}
return resultList;
}
/**
* 外部プロセスを実行する.
*
* @param command コマンド内容
* @return true: 正常終了, false: 異常終了
*/
private boolean processExec(List<String> command) {
// 処理結果
boolean result = false;
try {
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process Process = processBuilder.start();
// プロセスの正常終了まで待機させる
if (Process.waitFor() == 0) {
result = true;
log.info("Process Success: " + command.toString());
} else {
log.warn("Process Failed: " + command.toString());
}
// 標準出力
String strInput;
BufferedReader ipbr = new BufferedReader(new InputStreamReader(Process.getInputStream()));
while((strInput = ipbr.readLine()) != null) {
log.info(strInput);
}
ipbr.close();
// エラー出力
String strErr;
BufferedReader erbr = new BufferedReader(new InputStreamReader(Process.getErrorStream()));
while((strErr = erbr.readLine()) != null) {
log.info(strErr);
}
erbr.close();
//ProcessBuilderを使用後、バックグラウンドでInputStream, OutputStream, ErrorStreamがオープンされる.
// リソース不足を回避するため全てのストリームをクローズする.
Process.getInputStream().close();
Process.getOutputStream().close();
Process.getErrorStream().close();
} catch (InterruptedException | IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
return result;
}
/**
* 文字列をダブルクォーテーションで括る.
*
* @param str 文字列
* @return ダブルクォーテーションで括られた文字列
*/
private String includeDoubleQuotes(String str) {
return "\"" + str + "\"";
}
##【原因】証明書ファイルパスを「"」で括っていたため
メソッド:includeDoubleQuotesで、証明書ファイルパスを「"」で括っており、Javaアプリから実行されるコマンド
keytool -importkeystore -keystore "/.../XXXX.keystore" -srckeystore "/.../XXXX.p12" -srcstoretype PKCS12 -srcstorepass root -deststorepass changeit
が正常に実行されていなかったようです。
「"」を付与する関数を除外することで正しく動作しました。
開発環境がWindowsで、証明書の配置場所をProgram Files配下にしていました。
ファイルパスに半角スペースが含まれており、証明書のファイルパスを「"」で括らないと正常にコマンド実行されなかったので、関数をかませて「"」をつけるようにしていたのです。
こういう外部プロセス実行系の処理は要注意ですね。
たとえば、windowsにlinuxの仮想環境を作って、そこにデプロイして動作確認とかしないと危ないかも。。。