diff --git a/src/main/java/de/mlessmann/certassist/DeleteRecursiveFileVisitor.java b/src/main/java/de/mlessmann/certassist/DeleteRecursiveFileVisitor.java new file mode 100644 index 0000000..f8c501e --- /dev/null +++ b/src/main/java/de/mlessmann/certassist/DeleteRecursiveFileVisitor.java @@ -0,0 +1,47 @@ +package de.mlessmann.certassist; + +import org.slf4j.Logger; +import org.springframework.lang.NonNull; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +import static org.slf4j.LoggerFactory.getLogger; + +public class DeleteRecursiveFileVisitor implements FileVisitor { + + private static final Logger LOGGER = getLogger(DeleteRecursiveFileVisitor.class); + + @NonNull + @Override + public FileVisitResult preVisitDirectory(Path dir, @NonNull BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @NonNull + @Override + public FileVisitResult visitFile(Path file, @NonNull BasicFileAttributes attrs) throws IOException { + LOGGER.trace("Deleting file {}", file); + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @NonNull + @Override + public FileVisitResult visitFileFailed(Path file, @NonNull IOException exc) throws IOException { + LOGGER.error("Could not delete file {}", file, exc); + throw exc; + } + + @NonNull + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + LOGGER.trace("Deleting directory {}", dir); + Files.delete(dir); + return FileVisitResult.CONTINUE; + } +} diff --git a/src/main/java/de/mlessmann/certassist/except/UnresolvableCLIDependency.java b/src/main/java/de/mlessmann/certassist/except/UnresolvableCLIDependency.java index cbaab23..908823f 100644 --- a/src/main/java/de/mlessmann/certassist/except/UnresolvableCLIDependency.java +++ b/src/main/java/de/mlessmann/certassist/except/UnresolvableCLIDependency.java @@ -1,17 +1,16 @@ -package de.mlessmann.certassist.except; - -import lombok.Getter; - -public class UnresolvableCLIDependency extends Exception { - - @Getter - private final String executableName; - @Getter - private final String propertyName; - - public UnresolvableCLIDependency(String executableName, String propertyName) { - super("Could not resolve executable for '%s'. (Use property '%s' to point the application directly to the executable.)".formatted(executableName, propertyName)); - this.executableName = executableName; - this.propertyName = propertyName; - } -} +package de.mlessmann.certassist.except; + +import lombok.Getter; + +@Getter +public class UnresolvableCLIDependency extends Exception { + + private final String executableName; + private final String propertyName; + + public UnresolvableCLIDependency(String executableName, String propertyName) { + super("Could not resolve executable for '%s'. (Use property '%s' to point the application directly to the executable.)".formatted(executableName, propertyName)); + this.executableName = executableName; + this.propertyName = propertyName; + } +} diff --git a/src/main/java/de/mlessmann/certassist/openssl/CertificateRequest.java b/src/main/java/de/mlessmann/certassist/openssl/CertificateRequest.java index 6e9ab73..323a5cb 100644 --- a/src/main/java/de/mlessmann/certassist/openssl/CertificateRequest.java +++ b/src/main/java/de/mlessmann/certassist/openssl/CertificateRequest.java @@ -1,50 +1,36 @@ package de.mlessmann.certassist.openssl; import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; import java.util.UUID; - +@Data @Builder public class CertificateRequest { - @Getter - @Setter @Builder.Default private String oid = UUID.randomUUID().toString(); - @Getter - @Setter private RequestType type; - - @Getter - @Setter private String commonName; - - @Getter - @Setter private String trustingAuthority; - - @Getter - @Setter @Builder.Default private int requestedKeyLength = 4096; - - @Getter - @Setter @Builder.Default private int requestedValidityDays = 365; - - @Getter - @Setter - @Builder.Default - private CertificateSubject subject = CertificateSubject.builder().build(); + private CertificateSubject subject; public enum RequestType { ROOT_AUTHORITY, STANDALONE_CERTIFICATE, NORMAL_CERTIFICATE } + + public static class CertificateRequestBuilder { + public CertificateRequestBuilder subject(CertificateSubject.CertificateSubjectBuilder builder) { + this.subject = builder.build(); + return this; + } + } } diff --git a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java index 49d0466..2c43260 100644 --- a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java +++ b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateCreator.java @@ -33,25 +33,25 @@ public class OpenSSLCertificateCreator { private static String buildSubjectArg(CertificateRequest request) { String certSubject = OPENSSL_CERT_SUBJECT_TEMPLATE.replace("ISO-COUNTRY", request.getSubject() - .getCountry()) - .replace("STATE", request.getSubject() - .getState()) - .replace("LOCALITY", request.getSubject() - .getLocality()) - .replace("ORGANIZATION", request.getSubject() - .getOrganization()) - .replace("COMMON-NAME", request.getCommonName()); + .getCountry()) + .replace("STATE", request.getSubject() + .getState()) + .replace("LOCALITY", request.getSubject() + .getLocality()) + .replace("ORGANIZATION", request.getSubject() + .getOrganization()) + .replace("COMMON-NAME", request.getCommonName()); if (StringUtils.isNotBlank(request.getSubject() - .getOrganizationalUnit())) { + .getOrganizationalUnit())) { certSubject += "/OU=" + request.getSubject() - .getOrganizationalUnit(); + .getOrganizationalUnit(); } if (StringUtils.isNotBlank(request.getSubject() - .getEmailAddress())) { + .getEmailAddress())) { certSubject += "/emailAddress=" + request.getSubject() - .getEmailAddress(); + .getEmailAddress(); } return certSubject; } @@ -72,23 +72,23 @@ public class OpenSSLCertificateCreator { private Path createKeyfile(CertificateRequest request, Path tmpDir) throws CommandLineOperationException, InterruptedException { Path keyFile = tmpDir.resolve("root.key") - .toAbsolutePath(); + .toAbsolutePath(); LOGGER.atDebug() - .log("Writing new certificate key to {}", keyFile); + .log("Writing new certificate key to {}", keyFile); try { 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(); + 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(); + .get(); } catch (IOException e) { throw new CommandLineOperationException("Failure running OpenSSL keygen command.", e); } catch (ExecutionException e) { @@ -99,30 +99,30 @@ public class OpenSSLCertificateCreator { private Path createCertificate(CertificateRequest request, Path tmpDir) throws CommandLineOperationException, InterruptedException { Path keyFile = tmpDir.resolve("root.key") - .toAbsolutePath(); + .toAbsolutePath(); Path certFile = tmpDir.resolve("root.crt") - .toAbsolutePath(); + .toAbsolutePath(); LOGGER.atDebug() - .log("Writing new certificate file {}", certFile); + .log("Writing new certificate file {}", certFile); String certSubject = buildSubjectArg(request); try { - StartedProcess keygenProc = new ProcessExecutor().command(resolveOpenSSL(), "req", "x509", "-new", "-nodes", - "-key", keyFile.toString(), "-sha256", "-days", - Integer.toString( - request.getRequestedValidityDays()), - "-out", - certFile.toString(), - "-passout", "env:KEY_PASS", "-utf8", "-subj", - certSubject) - .environment("KEY_PASS", request.getOid()) - .redirectOutput(Slf4jStream.ofCaller() - .asDebug()) - .redirectError(Slf4jStream.ofCaller() - .asError()) - .start(); + StartedProcess keygenProc = new ProcessExecutor().command(resolveOpenSSL(), "req", "-new", "-nodes", + "-key", keyFile.toString(), "-sha256", "-days", + Integer.toString( + request.getRequestedValidityDays()), + "-out", + certFile.toString(), + "-passout", "env:KEY_PASS", "-utf8", "-subj", + certSubject) + .environment("KEY_PASS", request.getOid()) + .redirectOutput(Slf4jStream.ofCaller() + .asDebug()) + .redirectError(Slf4jStream.ofCaller() + .asError()) + .start(); keygenProc.getFuture() - .get(); + .get(); } catch (IOException e) { throw new CommandLineOperationException("Failure running OpenSSL req command.", e); } catch (ExecutionException e) { diff --git a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateResult.java b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateResult.java index ce69053..04a4833 100644 --- a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateResult.java +++ b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLCertificateResult.java @@ -1,10 +1,12 @@ package de.mlessmann.certassist.openssl; +import de.mlessmann.certassist.DeleteRecursiveFileVisitor; import org.slf4j.Logger; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Set; import static org.slf4j.LoggerFactory.getLogger; @@ -21,6 +23,6 @@ public class OpenSSLCertificateResult implements AutoCloseable { @Override public void close() throws IOException { LOGGER.info("Cleaning up temporary output directory {}", tmpDir); - Files.deleteIfExists(tmpDir); + Files.walkFileTree(tmpDir, Set.of(), Integer.MAX_VALUE, new DeleteRecursiveFileVisitor()); } } diff --git a/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java b/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java index 4363de8..70d38ae 100644 --- a/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java +++ b/src/test/java/de/mlessmann/certassist/TestOpenSSLCertificateCreator.java @@ -2,6 +2,7 @@ package de.mlessmann.certassist; import de.mlessmann.certassist.openssl.CertificateRequest; import de.mlessmann.certassist.openssl.CertificateRequest.RequestType; +import de.mlessmann.certassist.openssl.CertificateSubject; import de.mlessmann.certassist.openssl.OpenSSLCertificateCreator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,9 +20,11 @@ public class TestOpenSSLCertificateCreator { @Test void testCertificateCreation() throws Exception { CertificateRequest certRequest = CertificateRequest.builder() - .commonName("test.home") - .type(RequestType.STANDALONE_CERTIFICATE) - .build(); + .commonName("test.home") + .type(RequestType.STANDALONE_CERTIFICATE) + .subject(CertificateSubject.builder().country("DE").state("SH") + .locality("").organization("Crazy-Cats")) + .build(); try (var cert = openSSLCertificateCreator.createCertificate(certRequest)) { System.out.println("Certificate created: " + cert);