I'm trying to automatize the process of manual generation of .p12 signed certificate for web authorization.
KeyPair (.p12) and CertReq (.csr) files are generated using JDK's keytool program. Now I need to sign .p12 with an intermediate certificate using OpenSSL, but unlike executing "keytool" commands in ProcessBuilder
or in Runtime.getRuntime().exec(...)
, openssl struggles working through Java's Process object. I don't know what's wrong with it.
The command I need to be executed:
openssl ca -config ./CA_config.cnf -extensions my_client_cert -infiles ./CA_certreqs/MY_CLIENT_AAAAAA.csr
There are 3 moments when it waits for user input and outputs text in-between them:
The code snapshot is provided below. Most vars are replaced with hardcode for easier reading.
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;
}
}
}
Problem: I cannot make it reach the end, so it generates a new .pem file and/or outputs "BEGIN CERTIFICATE" text in the console for my further processing.
It doesn't reach even the first input spot where I need to enter CA_certificate.crt password. At best, I catch the first output line "Using configuration from ./CA_config.cnf".
I'm sure everything is set up fine.
What I've tried:
Any help or ideas how to make work fine other than deligating the command to a .bat file? Maybe I don't interact with the Process object's Input and Output streams in the right way.
Any help is appreciated!
OS: Windows 10 x64
It doesn't reach even the first input spot where I need to enter CA_certificate.crt password.
You use BufferedReader.readLine
which returns when, and only when, it reads a line terminator aka end-of-line/EOL -- usually LF on Unix, CRLF on Windows, or CR on classic Mac, but it accepts any of the three on any type of system -- or the input stream hits end-of-file/EOF, and since the connection from a child process' stdout or stderr (or both when you combine them) is a pipe, EOF occurs only when the child process closes its end of the pipe or exits. In general a prompt is neither terminated by LF/CRLF/CR nor followed immediately by closing or exiting, hence your code won't read it. But for this prompt see more below.
various ways of waiting for a while, so openssl would be ready to consume inputs from me
This is unnecessary and useless; the connection to a child process' stdin is also a pipe and pipes are buffered, so you can write at least a few kilobytes even if the child isn't ready to read yet.
Your actual problem is that openssl
passphrase prompts do not use stderr (or stdout) and stdin, but instead directly use the console, which ProcessBuilder
cannot capture or handle. Instead of trying to answer this prompt, add to your command two additional arguments -passin pass:<value>
.
After doing that you can answer y+EOL to (with or without previously reading) the 'sign' and 'commit' prompts, but more easily you can add another argument -batch
which skips these prompts entirely.
Aside: since you're using keytool already, have you considered using keytool -gencert
to generate the (child) cert from CSR instead of futzing with openssl
? It doesn't maintain a 'database' for you like openssl ca
does, but if you actually want that you can do it yourself in Java, and probably better.