feat: Implement verification of trust chains in OpenSSLCertificateCreator
This commit is contained in:
parent
2b6473929a
commit
e888ea57c1
3 changed files with 64 additions and 5 deletions
|
@ -7,6 +7,7 @@ import java.nio.file.Path;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
@ -16,6 +17,8 @@ import org.springframework.stereotype.Service;
|
|||
@Slf4j
|
||||
public class ExecutableResolver {
|
||||
|
||||
private static final AtomicBoolean loggedPath = new AtomicBoolean(false);
|
||||
|
||||
@Value("${openssl.path:#{null}}")
|
||||
private String opensslPath;
|
||||
|
||||
|
@ -39,6 +42,13 @@ public class ExecutableResolver {
|
|||
Objects.requireNonNull(envPath, "Environment variable 'PATH' is not set?!");
|
||||
String[] pathEntries = envPath.split(File.pathSeparator);
|
||||
|
||||
if (!loggedPath.get()) {
|
||||
loggedPath.set(true);
|
||||
for (String pathEntry : pathEntries) {
|
||||
log.atError().log("Path entry: {}", pathEntry);
|
||||
}
|
||||
}
|
||||
|
||||
for (String pathEntry : pathEntries) {
|
||||
for (String fileExtension : getAllowedExtensions()) {
|
||||
Path executablePath = Path.of(pathEntry, executableName + fileExtension);
|
||||
|
|
|
@ -2,6 +2,7 @@ package de.mlessmann.certassist.openssl;
|
|||
|
||||
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
|
||||
|
||||
import de.mlessmann.certassist.DeleteRecursiveFileVisitor;
|
||||
import de.mlessmann.certassist.ExecutableResolver;
|
||||
import de.mlessmann.certassist.except.CommandLineOperationException;
|
||||
import de.mlessmann.certassist.except.UnresolvableCLIDependency;
|
||||
|
@ -23,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.zeroturnaround.exec.ProcessExecutor;
|
||||
import org.zeroturnaround.exec.StartedProcess;
|
||||
import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
|
||||
|
@ -211,17 +213,56 @@ public class OpenSSLCertificateCreator {
|
|||
return outFile;
|
||||
}
|
||||
|
||||
public boolean verifyCertificate(Path certFile) throws CommandLineOperationException {
|
||||
public boolean verifyCertificate(@NonNull Path fullChainFile, @NonNull Path trustedCA)
|
||||
throws CommandLineOperationException {
|
||||
return verifyCertificate(fullChainFile, List.of(trustedCA));
|
||||
}
|
||||
|
||||
public boolean verifyCertificate(@NonNull Path fullChainFile, @NonNull List<Path> trustedCAs)
|
||||
throws CommandLineOperationException {
|
||||
if (CollectionUtils.isEmpty(trustedCAs)) {
|
||||
throw new IllegalArgumentException(
|
||||
"At least one trusted CA certificate must be provided to run the verification command."
|
||||
);
|
||||
}
|
||||
|
||||
Path tmpDir = null;
|
||||
try {
|
||||
Path tempTrustedBundle;
|
||||
if (trustedCAs.size() == 1) {
|
||||
tempTrustedBundle = trustedCAs.getFirst();
|
||||
} else {
|
||||
tmpDir = Files.createTempDirectory(CERTASSIST_TMP_PREFIX);
|
||||
tempTrustedBundle = tmpDir.resolve("trusted_bundle.pem");
|
||||
for (Path ca : trustedCAs) {
|
||||
Files.writeString(
|
||||
tempTrustedBundle,
|
||||
Files.readString(ca),
|
||||
StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.APPEND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StartedProcess verifyCommand = new ProcessExecutor()
|
||||
.command(resolveOpenSSL(), "x509", "-in", certFile.toString(), "-text", "-noout")
|
||||
.redirectOutput(Slf4jStream.ofCaller().asDebug())
|
||||
.command(resolveOpenSSL(), "verify", "-CAfile", tempTrustedBundle.toString(), fullChainFile.toString())
|
||||
.redirectOutput(Slf4jStream.ofCaller().asError())
|
||||
.redirectError(Slf4jStream.ofCaller().asError())
|
||||
.start();
|
||||
var verifyResult = verifyCommand.getFuture().get();
|
||||
return verifyResult.getExitValue() == 0;
|
||||
} catch (IOException | InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (tmpDir != null) {
|
||||
try {
|
||||
Files.walkFileTree(tmpDir, new DeleteRecursiveFileVisitor());
|
||||
Files.deleteIfExists(tmpDir);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to clean up temporary verification directory: {}", tmpDir, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ import static org.mockito.Mockito.*;
|
|||
|
||||
import de.mlessmann.certassist.openssl.*;
|
||||
import de.mlessmann.certassist.openssl.CertificateRequest.RequestType;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -37,7 +40,8 @@ class TestOpenSSLCertificateCreator {
|
|||
.build();
|
||||
|
||||
try (var cert = certificateCreator.createCertificate(certRequest)) {
|
||||
assertThat(certificateCreator.verifyCertificate(cert.certificatePath())).isEqualTo(true);
|
||||
assertThat(certificateCreator.verifyCertificate(cert.certificatePath(), cert.certificatePath()))
|
||||
.isEqualTo(true);
|
||||
System.out.println("Certificate created: " + cert);
|
||||
|
||||
CertificateRequest childRequest = CertificateRequest
|
||||
|
@ -55,7 +59,11 @@ class TestOpenSSLCertificateCreator {
|
|||
when(certificateProvider.requestCertificateUsage(cert.fingerprint())).thenReturn(spiedCert);
|
||||
try (var childCert = certificateCreator.createCertificate(childRequest)) {
|
||||
System.out.println("Child certificate created: " + childCert);
|
||||
assertThat(certificateCreator.verifyCertificate(childCert.certificatePath())).isEqualTo(true);
|
||||
Path fullchain = childCert.certificatePath().getParent().resolve("fullchain.pem");
|
||||
Files.write(fullchain, Files.readAllBytes(childCert.certificatePath()), StandardOpenOption.CREATE);
|
||||
Files.write(fullchain, Files.readAllBytes(cert.certificatePath()), StandardOpenOption.APPEND);
|
||||
|
||||
assertThat(certificateCreator.verifyCertificate(cert.certificatePath(), fullchain)).isEqualTo(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue