diff --git a/build.gradle.kts b/build.gradle.kts index 1251664..5e3766a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,7 +29,8 @@ repositories { } dependencies { - implementation("org.apache.commons:commons-lang3:3.17.0") + implementation("org.zeroturnaround:zt-exec:1.12") + implementation("org.apache.commons:commons-lang3:3.17.0") implementation("org.springframework.boot:spring-boot-autoconfigure") implementation("org.springframework.boot:spring-boot-starter-jdbc") @@ -54,4 +55,5 @@ dependencies { tasks.withType { useJUnitPlatform() + testLogging.showStandardStreams = true } diff --git a/src/main/java/de/mlessmann/certassist/except/CommandLineOperationException.java b/src/main/java/de/mlessmann/certassist/except/CommandLineOperationException.java new file mode 100644 index 0000000..10ca729 --- /dev/null +++ b/src/main/java/de/mlessmann/certassist/except/CommandLineOperationException.java @@ -0,0 +1,16 @@ +package de.mlessmann.certassist.except; + +public class CommandLineOperationException extends Exception { + + public CommandLineOperationException(String message) { + super(message); + } + + public CommandLineOperationException(String message, Throwable cause) { + super(message, cause); + } + + public CommandLineOperationException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java index adb7671..42d22fc 100644 --- a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java +++ b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java @@ -1,14 +1,20 @@ package de.mlessmann.certassist.openssl; import de.mlessmann.certassist.ExecutableResolver; +import de.mlessmann.certassist.except.CommandLineOperationException; import de.mlessmann.certassist.except.UnresolvableCLIDependency; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; +import org.zeroturnaround.exec.ProcessExecutor; +import org.zeroturnaround.exec.StartedProcess; +import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.concurrent.ExecutionException; import static org.slf4j.LoggerFactory.getLogger; @@ -24,39 +30,51 @@ public class OpenSSLCertificateCreator { this.executableResolver = executableResolver; } - public void createCertificate(CertificateRequest request) { + @Nullable + public OpenSSLCertificateResult createCertificate(CertificateRequest request) throws CommandLineOperationException, InterruptedException { Path tmpDir; try { tmpDir = Files.createTempDirectory("certassist"); } catch (IOException e) { - LOGGER.atError() - .log("Could not create temp directory for openssl generator!", e); - return; + throw new CommandLineOperationException("Could not create temporary directory for certificate creation", e); } + createKeyfile(request, tmpDir); + return new OpenSSLCertificateResult(tmpDir); + } + + private Path createKeyfile(CertificateRequest request, Path tmpDir) throws CommandLineOperationException, InterruptedException { + Path keyFile = tmpDir.resolve("root.key") + .toAbsolutePath(); + LOGGER.atDebug() + .log("Writing new certificate key to {}", keyFile); + try { - createKeyfile(request, tmpDir); - - } catch (IOException | InterruptedException e) { - LOGGER.atError() - .log(e.getMessage()); - } catch (UnresolvableCLIDependency e) { - LOGGER.atError() - .log(e.getMessage()); + StartedProcess keygenProc = new ProcessExecutor().command(resolveOpenSSL(), "genrsa", "-out", + keyFile.toString(), + "-passout", "env:KEY_PASS", + Integer.toString(request.getRequestedKeyLength())) + .environment("KEY_PASS", request.getOid()) + .redirectOutput(Slf4jStream.ofCaller() + .asDebug()) + .redirectError(Slf4jStream.ofCaller() + .asError()) + .start(); + keygenProc.getFuture() + .get(); + } catch (IOException e) { + throw new CommandLineOperationException("Failure running OpenSSL keygen command.", e); + } catch (ExecutionException e) { + throw new RuntimeException(e); } - } - - private Path createKeyfile(CertificateRequest request, Path tmpDir) throws UnresolvableCLIDependency, IOException, InterruptedException { - Path keyFile = tmpDir.resolve("root.key").toAbsolutePath(); - LOGGER.atDebug().log("Creating root certificate key at: {}", keyFile); - - String openSSLPath = executableResolver.getOpenSSLPath(); - Process createRootKeyProc = new ProcessBuilder() - .command(openSSLPath, "req", "genrsa", "-des3", "-out", keyFile.toString(), - Integer.toString(request.getRequestedKeyLength())) - .inheritIO() - .start(); - createRootKeyProc.waitFor(); return keyFile; } + + private String resolveOpenSSL() throws CommandLineOperationException { + try { + return executableResolver.getOpenSSLPath(); + } catch (UnresolvableCLIDependency e) { + throw new CommandLineOperationException(e); + } + } } diff --git a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateResult.java b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateResult.java new file mode 100644 index 0000000..ce69053 --- /dev/null +++ b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateResult.java @@ -0,0 +1,26 @@ +package de.mlessmann.certassist.openssl; + +import org.slf4j.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.slf4j.LoggerFactory.getLogger; + +public class OpenSSLCertificateResult implements AutoCloseable { + + private static final Logger LOGGER = getLogger(OpenSSLCertificateResult.class); + + private final Path tmpDir; + + OpenSSLCertificateResult(Path tmpDir) { + this.tmpDir = tmpDir; + } + + @Override + public void close() throws IOException { + LOGGER.info("Cleaning up temporary output directory {}", tmpDir); + Files.deleteIfExists(tmpDir); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1b73bda..177ac97 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,4 +8,8 @@ password=admin spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect #TODO: Use flyway for db setup hibernate.hbm2ddl.auto=create-drop -hibernate.show_sql=true \ No newline at end of file +hibernate.show_sql=true + +# Logging +logging.level.root=INFO +logging.level.de.mlessmann.certassist=DEBUG diff --git a/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java b/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java index 80dc058..4363de8 100644 --- a/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java +++ b/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java @@ -17,12 +17,14 @@ public class TestOpenSSLCertificateCreator { } @Test - void testCertificateCreation() { + void testCertificateCreation() throws Exception { CertificateRequest certRequest = CertificateRequest.builder() .commonName("test.home") .type(RequestType.STANDALONE_CERTIFICATE) .build(); - openSSLCertificateCreator.createCertificate(certRequest); + try (var cert = openSSLCertificateCreator.createCertificate(certRequest)) { + System.out.println("Certificate created: " + cert); + } } }