diff --git a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLService.java b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLService.java
index 1c09421..294a623 100644
--- a/src/main/java/de/mlessmann/certassist/openssl/OpenSSLService.java
+++ b/src/main/java/de/mlessmann/certassist/openssl/OpenSSLService.java
@@ -3,6 +3,7 @@ package de.mlessmann.certassist.openssl;
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.*;
+import static lombok.AccessLevel.PRIVATE;
import static org.slf4j.LoggerFactory.getLogger;
import de.mlessmann.certassist.DeleteRecursiveFileVisitor;
@@ -14,15 +15,14 @@ import de.mlessmann.certassist.models.CertificateInfoExtension;
import de.mlessmann.certassist.models.CertificateInfoSubject;
import de.mlessmann.certassist.models.CertificateInfoSubject.CertificateInfoSubjectBuilder;
import de.mlessmann.certassist.service.ExecutableResolver;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.cert.X509Certificate;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
+import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -49,9 +50,9 @@ import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
@Slf4j
public class OpenSSLService {
- private static final Logger openSSLLogger = getLogger("OpenSSL-Logger");
public static final String OPENSSL_CERT_SUBJECT_TEMPLATE =
"/C=ISO-COUNTRY/ST=STATE/L=LOCALITY/O=ORGANIZATION/CN=COMMON-NAME";
+ private static final Logger openSSLLogger = getLogger("OpenSSL-Logger");
private static final String CSR_EXT_TEMPLATE =
"""
authorityKeyIdentifier=keyid,issuer
@@ -369,6 +370,7 @@ public class OpenSSLService {
/**
* Verifies a passphrase against a provided key.
+ *
* @implNote Due to the implementation of the OpenSSL cli, any password will be valid for unencrypted keys. (Check with {@link #isKeyEncrypted(Path).)
*/
public boolean verifyKeyPassphrase(@NonNull Path keyFile, @NonNull String passphrase)
@@ -495,48 +497,40 @@ public class OpenSSLService {
public String getCertificateFingerprint(@NonNull Path certificate) throws CommandLineOperationException {
requireNonNull(certificate, "Certificate must be provided to generate fingerprint.");
- StartedProcess fingerprintProc = null;
try {
- fingerprintProc =
- new ProcessExecutor()
- .command(resolveOpenSSL(), "x509", "-in", certificate.toString(), "-noout", "-fingerprint")
- .readOutput(true)
- .redirectError(Slf4jStream.of(openSSLLogger).asError())
- .start();
- var fingerprintResult = fingerprintProc.getFuture().get(30, SECONDS);
- String output = fingerprintResult.getOutput().getUTF8();
-
- if (fingerprintResult.getExitValue() != 0) {
- log.debug("Fingerprint command output:\n{}", output);
- throw new CommandLineOperationException(
- "Failed to get fingerprint of certificate. Exit code: %d".formatted(
- fingerprintResult.getExitValue()
- )
- );
- }
-
- Matcher matcher = FINGERPRINT_EXTRACTOR.matcher(output);
- if (!matcher.find()) {
- log.debug(output);
- throw new CommandLineOperationException(
- "Unexpected output of fingerprint command. (See log for more details)"
- );
- }
- String algorithm = matcher.group("algo");
- String fingerprint = matcher.group("finger");
- if (StringUtils.isBlank(algorithm) || StringUtils.isBlank(fingerprint)) {
- throw new CommandLineOperationException(
- "Unexpected output of fingerprint command: %s %s".formatted(algorithm, fingerprint)
- );
- }
- return "%s;%s".formatted(algorithm, fingerprint);
- } catch (IOException | ExecutionException | TimeoutException | InterruptedException e) {
- throw new RuntimeException(e);
- } finally {
- killIfActive(fingerprintProc);
+ return getCertificateFingerprint(Files.readString(certificate));
+ } catch (IOException e) {
+ throw new CommandLineOperationException("Certificate content could not be read.", e);
}
}
+ private String extractFingerprintFromOutput(ProcessResult fingerprintResult) throws CommandLineOperationException {
+ String output = fingerprintResult.getOutput().getUTF8();
+
+ if (fingerprintResult.getExitValue() != 0) {
+ log.debug("Fingerprint command output:\n{}", output);
+ throw new CommandLineOperationException(
+ "Failed to get fingerprint of certificate. Exit code: %d".formatted(fingerprintResult.getExitValue())
+ );
+ }
+
+ Matcher matcher = FINGERPRINT_EXTRACTOR.matcher(output);
+ if (!matcher.find()) {
+ log.debug(output);
+ throw new CommandLineOperationException(
+ "Unexpected output of fingerprint command. (See log for more details)"
+ );
+ }
+ String algorithm = matcher.group("algo");
+ String fingerprint = matcher.group("finger");
+ if (StringUtils.isBlank(algorithm) || StringUtils.isBlank(fingerprint)) {
+ throw new CommandLineOperationException(
+ "Unexpected output of fingerprint command: %s %s".formatted(algorithm, fingerprint)
+ );
+ }
+ return "%s;%s".formatted(algorithm, fingerprint);
+ }
+
@NonNull
@SneakyThrows
public String getCertificateFingerPrint(X509Certificate jdkCert) {
@@ -551,71 +545,25 @@ public class OpenSSLService {
@SneakyThrows
public String getCertificateFingerprint(@NonNull String pemContent) throws CommandLineOperationException {
requireNonNull(pemContent, "Certificate PEM content must be provided to generate fingerprint from string.");
- Path tmpFile = Files.createTempFile(CERTASSIST_TMP_PREFIX, ".pem");
- try {
- Files.writeString(tmpFile, pemContent);
- return getCertificateFingerprint(tmpFile);
+
+ StartedProcess fingerprintProc = null;
+ try (var input = new ByteArrayInputStream(pemContent.getBytes())) {
+ fingerprintProc = Commands.fingerprintCommand(resolveOpenSSL()).redirectInput(input).start();
+ var fingerprintResult = fingerprintProc.getFuture().get(30, SECONDS);
+ return extractFingerprintFromOutput(fingerprintResult);
+ } catch (IOException | ExecutionException | TimeoutException | InterruptedException e) {
+ throw new RuntimeException(e);
} finally {
- try {
- Files.deleteIfExists(tmpFile);
- } catch (IOException e) {
- log.warn("Unable to delete temporary file, adding to shutdown hook. {}", tmpFile);
- tmpFile.toFile().deleteOnExit();
- }
+ killIfActive(fingerprintProc);
}
}
@NonNull
@SneakyThrows
public CertificateInfo getCertificateInfo(String pemContent) {
- Path tmpFile = Files.createTempFile(CERTASSIST_TMP_PREFIX, ".pem");
- try {
- Files.writeString(tmpFile, pemContent);
- return getCertificateInfo(tmpFile);
- } finally {
- try {
- Files.deleteIfExists(tmpFile);
- } catch (IOException e) {
- log.warn("Unable to delete temporary file, adding to shutdown hook. {}", tmpFile);
- tmpFile.toFile().deleteOnExit();
- }
- }
- }
-
- @NonNull
- public CertificateInfo getCertificateInfo(Path path) throws CommandLineOperationException {
- requireNonNull(path, "Certificate file must be provided to read the info.");
-
StartedProcess infoProc = null;
- try {
- infoProc =
- new ProcessExecutor()
- .command(
- resolveOpenSSL(),
- "x509",
- "-in",
- path.toString(),
- "-noout",
- "-dateopt",
- "iso_8601",
- "-fingerprint",
- "-subject",
- "-issuer",
- "-serial",
- "-dates",
- "-alias",
- "-email",
- "-purpose",
- "-ext",
- "subjectAltName",
- "-nameopt",
- "sep_multiline",
- "-nameopt",
- "lname"
- )
- .readOutput(true)
- .redirectError(Slf4jStream.of(openSSLLogger).asError())
- .start();
+ try (var input = new ByteArrayInputStream(pemContent.getBytes())) {
+ infoProc = Commands.infoCommand(resolveOpenSSL()).redirectInput(input).start();
var infoResult = infoProc.getFuture().get(30, SECONDS);
String output = infoResult.getOutput().getUTF8();
if (infoResult.getExitValue() != 0) {
@@ -632,6 +580,16 @@ public class OpenSSLService {
}
}
+ @NonNull
+ public CertificateInfo getCertificateInfo(Path path) throws CommandLineOperationException {
+ requireNonNull(path, "Certificate file must be provided to read the info.");
+ try {
+ return getCertificateInfo(Files.readString(path));
+ } catch (IOException e) {
+ throw new CommandLineOperationException("Failed to read certificate file.", e);
+ }
+ }
+
@NonNull
private String resolveOpenSSL() throws CommandLineOperationException {
try {
@@ -747,4 +705,48 @@ public class OpenSSLService {
killIfActive(keyReadProc);
}
}
+
+ @NoArgsConstructor(access = PRIVATE)
+ private static class Commands {
+
+ private static ProcessExecutor infoCommand(String openSSL, String... additArgs) {
+ List command = new ArrayList<>(
+ List.of(
+ openSSL,
+ "x509",
+ "-noout",
+ "-dateopt",
+ "iso_8601",
+ "-fingerprint",
+ "-subject",
+ "-issuer",
+ "-serial",
+ "-dates",
+ "-alias",
+ "-email",
+ "-purpose",
+ "-ext",
+ "subjectAltName",
+ "-nameopt",
+ "sep_multiline",
+ "-nameopt",
+ "lname"
+ )
+ );
+ command.addAll(Arrays.asList(additArgs));
+ return new ProcessExecutor()
+ .command(command)
+ .readOutput(true)
+ .redirectError(Slf4jStream.of(openSSLLogger).asError());
+ }
+
+ private static ProcessExecutor fingerprintCommand(String openSSL, String... additArgs) {
+ List command = new ArrayList<>(List.of(openSSL, "x509", "-noout", "-fingerprint"));
+ command.addAll(Arrays.asList(additArgs));
+ return new ProcessExecutor()
+ .command(command)
+ .readOutput(true)
+ .redirectError(Slf4jStream.of(openSSLLogger).asError());
+ }
+ }
}