我正在尝试自动生成用于Web授权的.p12签名证书的手动生成过程。
KeyPair(.p12)和CertReq(.csr)文件是使用JDK的KeyTool程序生成的。现在,我需要使用OpenSSL用一个中间证书来签署.p12,但与在ProcessBuilder
或e1d1e中执行“keyTool”命令不同,OpenSSL很难处理Java的进程对象。我不知道它出了什么问题。
我需要执行的命令如下:
openssl ca -config ./CA_config.cnf -extensions my_client_cert -infiles ./CA_certreqs/MY_CLIENT_AAAAAA.csr
它有3个时刻等待用户输入并在它们之间输出文本:
1.输入CA_certificate.crt密码(.crt文件CA_config.cnf指向);
1.签署证书-[y/n];
1.提交结果--[y/n]。
下面提供了代码快照。为了更容易阅读,大多数变量都被替换为硬代码。
private static void signCertReqWithOpenSSL2() throws IOException {
String command = "openssl ca -config ./CA_config.cnf -extensions my_client_cert -infiles ./CA_certreqs/MY_CLIENT_AAAAAA.csr"
String[] commandSeparated = command.split(" ");
//init cmd process
ProcessBuilder pb = new ProcessBuilder("cmd.exe");
pb.redirectErrorStream(true);
pb.directory(new File("../dir1/dir2/").getAbsoluteFile());
pb.command(commandSeparated);
Process process = pb.start();
try (InputStream in = process.getInputStream());
OutputStream out = process.getOutputStream()) {
System.out.println("--- begin---");
readAllConsoleOutputFromBuffer(in, 80); //93 bytes actually
//enter CA_certificate.crt password
enterUserInputToOutputStream(out, caPassword);
readAllConsoleOutputFromBuffer(in, 10); //350
//sign the certificate
enterUserInputToOutputStream(out, "y");
readAllConsoleOutputFromBuffer(in, 10); //56
//commit the certification
enterUserInputToOutputStream(out, "y");
readAllConsoleOutputFromBuffer(process.getInputStream(), 10); //4815
System.out.println("--- end ---");
}
process.destroy();
}
private static void enterUserInputToOutputStream(OutputStream out, String input) throws IOException {
out.write(String.format("%s%n", input).getBytes());
out.flush();
}
//if the stream has enough text to be printed (indicating that it's probably ready for user input), print it
private static void readAllConsoleOutputFromBuffer(InputStream in, int minTextSizeInBytes) throws IOException {
//loop is made just to make it scanning the stream during some time. I know there're better ways
for (int i = 0; i < 100000; i++) {
if (in.available() > minTextSizeInBytes) {
String line;
BufferedReader buff = new BufferedReader(new InputStreamReader(in));
while ((line = buff.readLine()) != null) {
System.out.println(line);
}
break;
}
}
}
问题:我不能让它到达末尾,所以它生成一个新的.pem文件和/或在控制台中输出“Begin certifect”文本以供我进一步处理。
它甚至没有到达我需要输入CA_certificate.crt密码的第一个输入点。充其量,我捕捉到了第一个输出行“Using configuration from./CA_config.cnf”。
我相信一切都安排得很好。
- %PATH%;中存在OpenSSL目录
- 所有文件和文件夹都存在,OpenSSL会找到它们(如果我在CA_config.cnf中出错或删除了执行所需的任何文件,我会在控制台输出中发现找不到某些东西的错误)。
我试过的是:
- 忽略控制台输出(与InputStream交互);
- 等待一段时间的各种方式,这样OpenSSL就可以准备好消费来自我的输入(Thread.Slear,其他线程检查条件或睡眠,for loop进行一些时间流逝等);
- 使用openssl.exe作为可执行文件,而不是cmd.exe-我重写了命令和CA_config.cnf中的路径,并获得了与cmd.exe及其相对路径相同的结果。
- 处理字符串和编码,以防它在读取第一个输出行后以某种方式卡在行终止符,即使我怀疑这是根本原因。
除了将命令分离到.bat文件之外,还有什么帮助或想法可以让它正常工作吗?也许我没有以正确的方式与Process对象的输入流和输出流交互。
如有任何帮助,我们不胜感激!
操作系统:Windows 10 x64
1条答案
按热度按时间0lvr5msh1#
它甚至没有到达我需要输入CA_certificate.crt密码的第一个输入点。
您可以使用
BufferedReader.readLine
,当且仅当它读取行终止符(也就是行尾/EOL)时,它才会返回--在Unix上通常是LF,在Windows上通常是CRLF,在经典Mac上通常是CR,但在任何类型的系统上,它都可以接受这三种类型中的任何一种--或者输入流命中文件结尾/EOF,并且由于从子进程的stdout或stderr(如果将它们组合在一起)的连接是管道,只有当子进程关闭其管道末端或退出时才会发生EOF。通常,提示既不会以LF/CRLF/CR结束,也不会紧跟在关闭或退出之后,因此您的代码不会读取它。但对于This提示,请参见下面的更多内容。等待一段时间的各种方式,以便OpenSSL准备使用来自我的输入
这是不必要的,也是无用的;与子进程的stdin的连接也是一个管道,管道是缓冲的,所以即使子进程还没有准备好读取,您也可以写入至少几千字节。
您的实际问题是
openssl
passphrase提示不使用stderr(或stdout)和stdin,而是直接使用ProcessBuilder
无法捕获或处理的控制台。与其尝试回答这个提示,不如在命令中添加两个额外的参数-passin pass:<value>
。这样做之后,您可以回答y+eol(无论是否事先阅读)‘Sign’和‘Commit’提示,但更容易的是,您可以添加另一个*参数
-batch
,它完全跳过这些提示。旁白:既然您已经在使用KeyTool,您是否考虑过使用
keytool -gencert
从CSR生成(子)证书,而不是使用openssl
?它不像openssl ca
那样为你维护一个‘数据库’,但如果你真的想要,你可以用Java自己做,甚至可能更好。