chore: Update termination/exception handling
This commit is contained in:
parent
a78f815a76
commit
3f7e41b245
2 changed files with 57 additions and 50 deletions
|
@ -1,6 +1,7 @@
|
||||||
package de.mlessmann.certassist.openssl;
|
package de.mlessmann.certassist.openssl;
|
||||||
|
|
||||||
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
|
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
|
||||||
|
import static java.util.concurrent.TimeUnit.*;
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
import static org.slf4j.LoggerFactory.getLogger;
|
||||||
|
|
||||||
import de.mlessmann.certassist.DeleteRecursiveFileVisitor;
|
import de.mlessmann.certassist.DeleteRecursiveFileVisitor;
|
||||||
|
@ -18,7 +19,6 @@ 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.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;
|
||||||
|
@ -95,7 +95,7 @@ public class OpenSSLCertificateCreator {
|
||||||
try {
|
try {
|
||||||
log.debug("Process is still alive. Asking politely for it to destroy itself and waiting on exit for 2s");
|
log.debug("Process is still alive. Asking politely for it to destroy itself and waiting on exit for 2s");
|
||||||
sysProc.destroy();
|
sysProc.destroy();
|
||||||
sysProc.waitFor(2_000, TimeUnit.MILLISECONDS);
|
sysProc.waitFor(2_000, MILLISECONDS);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
log.debug("Interrupted while waiting for process to terminate. Registering forceful termination onExit", e);
|
log.debug("Interrupted while waiting for process to terminate. Registering forceful termination onExit", e);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(sysProc::destroyForcibly));
|
Runtime.getRuntime().addShutdownHook(new Thread(sysProc::destroyForcibly));
|
||||||
|
@ -103,8 +103,7 @@ public class OpenSSLCertificateCreator {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public OpenSSLCertificateResult createCertificate(CertificateRequest request)
|
public OpenSSLCertificateResult createCertificate(CertificateRequest request) throws CommandLineOperationException {
|
||||||
throws CommandLineOperationException, InterruptedException {
|
|
||||||
Path tmpDir;
|
Path tmpDir;
|
||||||
try {
|
try {
|
||||||
tmpDir = Files.createTempDirectory(CERTASSIST_TMP_PREFIX);
|
tmpDir = Files.createTempDirectory(CERTASSIST_TMP_PREFIX);
|
||||||
|
@ -151,12 +150,14 @@ public class OpenSSLCertificateCreator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path createKeyfile(CertificateRequest request, Path outFile, String filePassword)
|
private Path createKeyfile(CertificateRequest request, Path outFile, String filePassword)
|
||||||
throws CommandLineOperationException, InterruptedException {
|
throws CommandLineOperationException {
|
||||||
Path keyFile = outFile.toAbsolutePath();
|
Path keyFile = outFile.toAbsolutePath();
|
||||||
log.debug("Writing new certificate key to {}", keyFile);
|
log.debug("Writing new certificate key to {}", keyFile);
|
||||||
|
|
||||||
|
StartedProcess keygenProc = null;
|
||||||
try {
|
try {
|
||||||
StartedProcess keygenProc = new ProcessExecutor()
|
keygenProc =
|
||||||
|
new ProcessExecutor()
|
||||||
.command(
|
.command(
|
||||||
resolveOpenSSL(),
|
resolveOpenSSL(),
|
||||||
"genrsa",
|
"genrsa",
|
||||||
|
@ -171,17 +172,17 @@ 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();
|
||||||
keygenProc.getFuture().get();
|
keygenProc.getFuture().get(3, MINUTES);
|
||||||
} catch (IOException e) {
|
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
|
||||||
throw new CommandLineOperationException("Failure running OpenSSL keygen command.", e);
|
throw new CommandLineOperationException("Failure running OpenSSL keygen command.", e);
|
||||||
} catch (ExecutionException e) {
|
} finally {
|
||||||
throw new RuntimeException(e);
|
killIfActive(keygenProc);
|
||||||
}
|
}
|
||||||
return keyFile;
|
return keyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path createCertificate(CertificateRequest request, Path keyFile, Path outFile, String keyPassphrase)
|
private Path createCertificate(CertificateRequest request, Path keyFile, Path outFile, String keyPassphrase)
|
||||||
throws CommandLineOperationException, InterruptedException {
|
throws CommandLineOperationException {
|
||||||
log.debug("Writing new certificate file {}", outFile);
|
log.debug("Writing new certificate file {}", outFile);
|
||||||
|
|
||||||
String certSubject = buildSubjectArg(request);
|
String certSubject = buildSubjectArg(request);
|
||||||
|
@ -211,11 +212,9 @@ 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, SECONDS);
|
||||||
} catch (IOException e) {
|
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
|
||||||
throw new CommandLineOperationException("Failure running OpenSSL req command.", e);
|
throw new CommandLineOperationException("Failure running OpenSSL req command.", e);
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
killIfActive(certGenProc);
|
killIfActive(certGenProc);
|
||||||
}
|
}
|
||||||
|
@ -223,12 +222,14 @@ public class OpenSSLCertificateCreator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path createSigningRequest(CertificateRequest request, Path keyFile, Path outFile, String certPassword)
|
private Path createSigningRequest(CertificateRequest request, Path keyFile, Path outFile, String certPassword)
|
||||||
throws CommandLineOperationException, InterruptedException {
|
throws CommandLineOperationException {
|
||||||
log.atDebug().log("Writing new certificate signing request file {}", outFile);
|
log.atDebug().log("Writing new certificate signing request 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",
|
||||||
|
@ -248,11 +249,11 @@ 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, SECONDS);
|
||||||
} catch (IOException e) {
|
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
|
||||||
throw new CommandLineOperationException("Failure running OpenSSL req command.", e);
|
throw new CommandLineOperationException("Failure running OpenSSL req command.", e);
|
||||||
} catch (ExecutionException e) {
|
} finally {
|
||||||
throw new RuntimeException(e);
|
killIfActive(certGenProc);
|
||||||
}
|
}
|
||||||
return outFile;
|
return outFile;
|
||||||
}
|
}
|
||||||
|
@ -271,6 +272,7 @@ public class OpenSSLCertificateCreator {
|
||||||
}
|
}
|
||||||
|
|
||||||
Path tmpDir = null;
|
Path tmpDir = null;
|
||||||
|
StartedProcess verifyCommand = null;
|
||||||
try {
|
try {
|
||||||
Path tempTrustedBundle;
|
Path tempTrustedBundle;
|
||||||
if (trustedCAs.size() == 1) {
|
if (trustedCAs.size() == 1) {
|
||||||
|
@ -289,16 +291,18 @@ public class OpenSSLCertificateCreator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StartedProcess verifyCommand = new ProcessExecutor()
|
verifyCommand =
|
||||||
|
new ProcessExecutor()
|
||||||
.command(resolveOpenSSL(), "verify", "-CAfile", tempTrustedBundle.toString(), fullChainFile.toString())
|
.command(resolveOpenSSL(), "verify", "-CAfile", tempTrustedBundle.toString(), fullChainFile.toString())
|
||||||
.redirectOutput(Slf4jStream.of(openSSLLogger).asError())
|
.redirectOutput(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.start();
|
.start();
|
||||||
var verifyResult = verifyCommand.getFuture().get();
|
var verifyResult = verifyCommand.getFuture().get(30, SECONDS);
|
||||||
return verifyResult.getExitValue() == 0;
|
return verifyResult.getExitValue() == 0;
|
||||||
} catch (IOException | InterruptedException | ExecutionException e) {
|
} catch (IOException | InterruptedException | ExecutionException | TimeoutException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
killIfActive(verifyCommand);
|
||||||
if (tmpDir != null) {
|
if (tmpDir != null) {
|
||||||
try {
|
try {
|
||||||
Files.walkFileTree(tmpDir, new DeleteRecursiveFileVisitor());
|
Files.walkFileTree(tmpDir, new DeleteRecursiveFileVisitor());
|
||||||
|
@ -313,7 +317,7 @@ public class OpenSSLCertificateCreator {
|
||||||
/**
|
/**
|
||||||
* Checks whether the provided key file is encrypted using a passphrase
|
* Checks whether the provided key file is encrypted using a passphrase
|
||||||
*/
|
*/
|
||||||
public boolean isKeyEncrypted(@NonNull Path keyFile) throws InterruptedException, CommandLineOperationException {
|
public boolean isKeyEncrypted(@NonNull Path keyFile) throws CommandLineOperationException {
|
||||||
// If the key is not encrypted, any passphrase will work -> so generate a random one to check.
|
// If the key is not encrypted, any passphrase will work -> so generate a random one to check.
|
||||||
String passphrase = UUID.randomUUID().toString();
|
String passphrase = UUID.randomUUID().toString();
|
||||||
boolean firstPass = verifyKeyPassphrase(keyFile, passphrase);
|
boolean firstPass = verifyKeyPassphrase(keyFile, passphrase);
|
||||||
|
@ -330,9 +334,11 @@ public class OpenSSLCertificateCreator {
|
||||||
* @implNote Due to the implementation of the OpenSSL cli, <em>any password</em> will be valid for unencrypted keys. (Check with {@link #isKeyEncrypted(Path).)
|
* @implNote Due to the implementation of the OpenSSL cli, <em>any password</em> will be valid for unencrypted keys. (Check with {@link #isKeyEncrypted(Path).)
|
||||||
*/
|
*/
|
||||||
public boolean verifyKeyPassphrase(@NonNull Path keyFile, @NonNull String passphrase)
|
public boolean verifyKeyPassphrase(@NonNull Path keyFile, @NonNull String passphrase)
|
||||||
throws CommandLineOperationException, InterruptedException {
|
throws CommandLineOperationException {
|
||||||
|
StartedProcess verifyCommand = null;
|
||||||
try {
|
try {
|
||||||
StartedProcess verifyCommand = new ProcessExecutor()
|
verifyCommand =
|
||||||
|
new ProcessExecutor()
|
||||||
.command(
|
.command(
|
||||||
resolveOpenSSL(),
|
resolveOpenSSL(),
|
||||||
"rsa",
|
"rsa",
|
||||||
|
@ -346,10 +352,12 @@ public class OpenSSLCertificateCreator {
|
||||||
.redirectOutput(Slf4jStream.of(openSSLLogger).asError())
|
.redirectOutput(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.start();
|
.start();
|
||||||
var verifyResult = verifyCommand.getFuture().get();
|
var verifyResult = verifyCommand.getFuture().get(30, SECONDS);
|
||||||
return verifyResult.getExitValue() == 0;
|
return verifyResult.getExitValue() == 0;
|
||||||
} catch (IOException | InterruptedException | ExecutionException e) {
|
} catch (IOException | InterruptedException | ExecutionException | TimeoutException e) {
|
||||||
throw new CommandLineOperationException("Failed to verify key encryption", e);
|
throw new CommandLineOperationException("Failed to verify key encryption", e);
|
||||||
|
} finally {
|
||||||
|
killIfActive(verifyCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,7 +367,7 @@ public class OpenSSLCertificateCreator {
|
||||||
Path caKey,
|
Path caKey,
|
||||||
String caKeyPassphrase,
|
String caKeyPassphrase,
|
||||||
Path csrFile
|
Path csrFile
|
||||||
) throws CommandLineOperationException, InterruptedException {
|
) throws CommandLineOperationException {
|
||||||
Path outFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".crt"));
|
Path outFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".crt"));
|
||||||
log.debug("Writing new signed certificate file {}", outFile);
|
log.debug("Writing new signed certificate file {}", outFile);
|
||||||
Path extFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".ext"));
|
Path extFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".ext"));
|
||||||
|
@ -422,31 +430,31 @@ 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();
|
||||||
ProcessResult result = certGenProc.getFuture().get(30, TimeUnit.SECONDS);
|
ProcessResult result = certGenProc.getFuture().get(30, SECONDS);
|
||||||
// Check exit code
|
// Check exit code
|
||||||
if (result.getExitValue() != 0) {
|
if (result.getExitValue() != 0) {
|
||||||
throw new CommandLineOperationException("Failed to sign certificate. Exit code: " + result.getExitValue());
|
throw new CommandLineOperationException(
|
||||||
|
"Failed to sign certificate. Exit code: " + result.getExitValue()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
} catch (IOException | TimeoutException | ExecutionException | InterruptedException 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) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
killIfActive(certGenProc);
|
killIfActive(certGenProc);
|
||||||
}
|
}
|
||||||
return outFile;
|
return outFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCertificateFingerprint(Path certificate)
|
public String getCertificateFingerprint(Path certificate) throws CommandLineOperationException {
|
||||||
throws CommandLineOperationException, InterruptedException {
|
StartedProcess fingerprintProc = null;
|
||||||
try {
|
try {
|
||||||
StartedProcess fingerprintProc = new ProcessExecutor()
|
fingerprintProc =
|
||||||
|
new ProcessExecutor()
|
||||||
.command(resolveOpenSSL(), "x509", "-in", certificate.toString(), "-noout", "-fingerprint")
|
.command(resolveOpenSSL(), "x509", "-in", certificate.toString(), "-noout", "-fingerprint")
|
||||||
.readOutput(true)
|
.readOutput(true)
|
||||||
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.start();
|
.start();
|
||||||
var fingerprintResult = fingerprintProc.getFuture().get();
|
var fingerprintResult = fingerprintProc.getFuture().get(30, SECONDS);
|
||||||
String output = fingerprintResult.getOutput().getUTF8();
|
String output = fingerprintResult.getOutput().getUTF8();
|
||||||
|
|
||||||
if (fingerprintResult.getExitValue() != 0) {
|
if (fingerprintResult.getExitValue() != 0) {
|
||||||
|
@ -473,14 +481,18 @@ public class OpenSSLCertificateCreator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return "%s;%s".formatted(algorithm, fingerprint);
|
return "%s;%s".formatted(algorithm, fingerprint);
|
||||||
} catch (IOException | ExecutionException e) {
|
} catch (IOException | ExecutionException | TimeoutException | InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
killIfActive(fingerprintProc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CertificateRequest getCertificateInfo(Path path) throws CommandLineOperationException, InterruptedException {
|
public CertificateRequest getCertificateInfo(Path path) throws CommandLineOperationException {
|
||||||
|
StartedProcess infoProc = null;
|
||||||
try {
|
try {
|
||||||
StartedProcess infoProc = new ProcessExecutor()
|
infoProc =
|
||||||
|
new ProcessExecutor()
|
||||||
.command(
|
.command(
|
||||||
resolveOpenSSL(),
|
resolveOpenSSL(),
|
||||||
"x509",
|
"x509",
|
||||||
|
@ -507,7 +519,7 @@ public class OpenSSLCertificateCreator {
|
||||||
.readOutput(true)
|
.readOutput(true)
|
||||||
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.start();
|
.start();
|
||||||
var infoResult = infoProc.getFuture().get();
|
var infoResult = infoProc.getFuture().get(30, SECONDS);
|
||||||
String output = infoResult.getOutput().getUTF8();
|
String output = infoResult.getOutput().getUTF8();
|
||||||
if (infoResult.getExitValue() != 0) {
|
if (infoResult.getExitValue() != 0) {
|
||||||
log.debug("Certificate info command output:\n{}", output);
|
log.debug("Certificate info command output:\n{}", output);
|
||||||
|
@ -516,7 +528,7 @@ public class OpenSSLCertificateCreator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return getCertificateInfo(output.lines().toArray(String[]::new));
|
return getCertificateInfo(output.lines().toArray(String[]::new));
|
||||||
} catch (IOException | ExecutionException e) {
|
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,6 @@ public class CertificateCreationService {
|
||||||
) {
|
) {
|
||||||
certificate.setPrivateKey(Files.readAllBytes(certificateCreatorResult.certificateKeyPath()));
|
certificate.setPrivateKey(Files.readAllBytes(certificateCreatorResult.certificateKeyPath()));
|
||||||
certificate.setCert(Files.readAllBytes(certificateCreatorResult.certificatePath()));
|
certificate.setCert(Files.readAllBytes(certificateCreatorResult.certificatePath()));
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new IllegalStateException("Interrupted exception", e);
|
|
||||||
} catch (CommandLineOperationException | IOException e) {
|
} catch (CommandLineOperationException | IOException e) {
|
||||||
throw new IllegalStateException("Failed to create certificate!", e);
|
throw new IllegalStateException("Failed to create certificate!", e);
|
||||||
}
|
}
|
||||||
|
@ -81,8 +78,6 @@ public class CertificateCreationService {
|
||||||
return certificateRepository.save(entity);
|
return certificateRepository.save(entity);
|
||||||
} catch (CommandLineOperationException | IOException e) {
|
} catch (CommandLineOperationException | IOException e) {
|
||||||
throw new RuntimeException("Unable to import certificate", e);
|
throw new RuntimeException("Unable to import certificate", e);
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue