chore: Refactor how OpenSSL processes are started

This commit is contained in:
Magnus Leßmann (@MarkL4YG) 2024-11-23 13:11:37 +01:00
parent 97eea3a20f
commit 8ef6234bc5

View file

@ -18,6 +18,8 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -54,6 +56,8 @@ public class OpenSSLCertificateCreator {
private static final Pattern FINGERPRINT_EXTRACTOR = Pattern.compile( private static final Pattern FINGERPRINT_EXTRACTOR = Pattern.compile(
"^(?<algo>[0-9a-zA-Z]+) (?i)Fingerprint(?-i)=(?<finger>[a-z:A-Z0-9]+)" "^(?<algo>[0-9a-zA-Z]+) (?i)Fingerprint(?-i)=(?<finger>[a-z:A-Z0-9]+)"
); );
private static final String OSSL_ENV_KEY_PW = "KEY_PASS";
private static final String OSSL_ARG_KEY_PW = "env:" + OSSL_ENV_KEY_PW;
private final AtomicBoolean versionLogged = new AtomicBoolean(false); private final AtomicBoolean versionLogged = new AtomicBoolean(false);
private final ExecutableResolver executableResolver; private final ExecutableResolver executableResolver;
@ -78,6 +82,25 @@ public class OpenSSLCertificateCreator {
return certSubject; return certSubject;
} }
private static void killIfActive(StartedProcess process) {
if (process == null) {
return;
}
Process sysProc = process.getProcess();
if (!sysProc.isAlive()) {
return;
}
try {
log.debug("Process is still alive. Asking politely for it to destroy itself and waiting on exit for 2s");
sysProc.destroy();
sysProc.waitFor(2_000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.debug("Interrupted while waiting for process to terminate. Registering forceful termination onExit", e);
Runtime.getRuntime().addShutdownHook(new Thread(sysProc::destroyForcibly));
}
}
@NonNull @NonNull
public OpenSSLCertificateResult createCertificate(CertificateRequest request) public OpenSSLCertificateResult createCertificate(CertificateRequest request)
throws CommandLineOperationException, InterruptedException { throws CommandLineOperationException, InterruptedException {
@ -88,28 +111,28 @@ public class OpenSSLCertificateCreator {
throw new CommandLineOperationException("Could not create temporary directory for certificate creation", e); throw new CommandLineOperationException("Could not create temporary directory for certificate creation", e);
} }
String keypassphrase = passwordProvider.generateNewPassword(); String keyPassphrase = passwordProvider.generateNewPassword();
Path keyFile = createKeyfile(request, tmpDir.resolve("certificate.key"), keypassphrase); Path keyFile = createKeyfile(request, tmpDir.resolve("certificate.key"), keyPassphrase);
if ( if (
request.getType() == RequestType.ROOT_AUTHORITY || request.getType() == RequestType.STANDALONE_CERTIFICATE request.getType() == RequestType.ROOT_AUTHORITY || request.getType() == RequestType.STANDALONE_CERTIFICATE
) { ) {
Path certificate = createCertificate(request, keyFile, tmpDir.resolve("certificate.crt"), keypassphrase); Path certificate = createCertificate(request, keyFile, tmpDir.resolve("certificate.crt"), keyPassphrase);
String fingerprint = getCertificateFingerprint(certificate); String fingerprint = getCertificateFingerprint(certificate);
passwordProvider.setPasswordFor(fingerprint, keypassphrase); passwordProvider.setPasswordFor(fingerprint, keyPassphrase);
return new OpenSSLCertificateResult(tmpDir, certificate, keyFile, certificate, fingerprint); return new OpenSSLCertificateResult(tmpDir, certificate, keyFile, certificate, fingerprint);
} }
try (var certAuthority = certificateProvider.requestCertificateUsage(request.getTrustingAuthority())) { try (var certAuthority = certificateProvider.requestCertificateUsage(request.getTrustingAuthority())) {
Path unsignedCert = createSigningRequest(request, keyFile, tmpDir.resolve("child.csr"), keypassphrase); Path unsignedCert = createSigningRequest(request, keyFile, tmpDir.resolve("child.csr"), keyPassphrase);
Path signedCert = signCertificate( Path signedCert = signCertificate(
request, request,
certAuthority.certificatePath(), certAuthority.certificatePath(),
certAuthority.certificateKeyPath(), certAuthority.certificateKeyPath(),
unsignedCert, unsignedCert,
keypassphrase keyPassphrase
); );
String fingerprint = getCertificateFingerprint(signedCert); String fingerprint = getCertificateFingerprint(signedCert);
passwordProvider.setPasswordFor(fingerprint, keypassphrase); passwordProvider.setPasswordFor(fingerprint, keyPassphrase);
Path fullchain = tmpDir.resolve("fullchain.pem"); Path fullchain = tmpDir.resolve("fullchain.pem");
try { try {
@ -140,10 +163,10 @@ public class OpenSSLCertificateCreator {
keyFile.toString(), keyFile.toString(),
"-aes256", "-aes256",
"-passout", "-passout",
"env:KEY_PASS", OSSL_ARG_KEY_PW,
Integer.toString(request.getRequestedKeyLength()) Integer.toString(request.getRequestedKeyLength())
) )
.environment("KEY_PASS", filePassword) .environment(OSSL_ENV_KEY_PW, filePassword)
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug()) .redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
.redirectError(Slf4jStream.of(openSSLLogger).asError()) .redirectError(Slf4jStream.of(openSSLLogger).asError())
.start(); .start();
@ -156,20 +179,22 @@ public class OpenSSLCertificateCreator {
return keyFile; return keyFile;
} }
private Path createCertificate(CertificateRequest request, Path keyFile, Path outFile, String certPassword) private Path createCertificate(CertificateRequest request, Path keyFile, Path outFile, String keyPassphrase)
throws CommandLineOperationException, InterruptedException { throws CommandLineOperationException, InterruptedException {
log.debug("Writing new certificate file {}", outFile); log.debug("Writing new certificate file {}", outFile);
String certSubject = buildSubjectArg(request); String certSubject = buildSubjectArg(request);
StartedProcess certGenProc = null;
try { try {
StartedProcess certGenProc = new ProcessExecutor() certGenProc =
new ProcessExecutor()
.command( .command(
resolveOpenSSL(), resolveOpenSSL(),
"req", "req",
"-x509", "-x509",
"-new", "-new",
"-passin", "-passin",
"env:KEY_PASS", OSSL_ARG_KEY_PW,
"-key", "-key",
keyFile.toString(), keyFile.toString(),
"-sha256", "-sha256",
@ -177,13 +202,11 @@ public class OpenSSLCertificateCreator {
Integer.toString(request.getRequestedValidityDays()), Integer.toString(request.getRequestedValidityDays()),
"-out", "-out",
outFile.toString(), outFile.toString(),
"-passout",
"env:KEY_PASS",
"-utf8", "-utf8",
"-subj", "-subj",
certSubject certSubject
) )
.environment("KEY_PASS", certPassword) .environment(OSSL_ENV_KEY_PW, keyPassphrase)
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug()) .redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
.redirectError(Slf4jStream.of(openSSLLogger).asError()) .redirectError(Slf4jStream.of(openSSLLogger).asError())
.start(); .start();
@ -192,6 +215,8 @@ public class OpenSSLCertificateCreator {
throw new CommandLineOperationException("Failure running OpenSSL req command.", e); throw new CommandLineOperationException("Failure running OpenSSL req command.", e);
} catch (ExecutionException e) { } catch (ExecutionException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
killIfActive(certGenProc);
} }
return outFile; return outFile;
} }
@ -208,19 +233,17 @@ public class OpenSSLCertificateCreator {
"req", "req",
"-new", "-new",
"-passin", "-passin",
"env:KEY_PASS", OSSL_ARG_KEY_PW,
"-key", "-key",
keyFile.toString(), keyFile.toString(),
"-sha256", "-sha256",
"-out", "-out",
outFile.toString(), outFile.toString(),
"-passout",
"env:KEY_PASS",
"-utf8", "-utf8",
"-subj", "-subj",
certSubject certSubject
) )
.environment("KEY_PASS", certPassword) .environment(OSSL_ENV_KEY_PW, certPassword)
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug()) .redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
.redirectError(Slf4jStream.of(openSSLLogger).asError()) .redirectError(Slf4jStream.of(openSSLLogger).asError())
.start(); .start();
@ -393,8 +416,8 @@ public class OpenSSLCertificateCreator {
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug()) .redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
.redirectError(Slf4jStream.of(openSSLLogger).asError()) .redirectError(Slf4jStream.of(openSSLLogger).asError())
.start(); .start();
certGenProc.getFuture().get(); certGenProc.getFuture().get(30, TimeUnit.SECONDS);
} catch (IOException e) { } catch (IOException | TimeoutException e) {
throw new CommandLineOperationException("Failure running OpenSSL x509 command.", e); throw new CommandLineOperationException("Failure running OpenSSL x509 command.", e);
} catch (ExecutionException e) { } catch (ExecutionException e) {
throw new RuntimeException(e); throw new RuntimeException(e);