feat: Implement working draft of importing existing certificates #15

Merged
MarkL4YG merged 4 commits from feat/certificateImport into main 2024-11-23 10:51:57 +00:00
21 changed files with 577 additions and 47 deletions

5
.gitattributes vendored
View file

@ -2,4 +2,7 @@
*.bat text eol=crlf
*.jar binary
*.java text eol=lf
*.config text eol=lf
*.config text eol=lf
*.pem text eol=lf
*.srl text eol=lf
*.md text eol=lf

View file

@ -11,7 +11,7 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:

View file

@ -11,7 +11,7 @@ concurrency:
jobs:
check-formatting:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:

5
.gitignore vendored
View file

@ -37,5 +37,6 @@ out/
### VS Code ###
.vscode/
### SQLite DB ###
sqLiteDb.db
### Test files ###
sqLiteDb.db
dev/

6
docs/limitations.md Normal file
View file

@ -0,0 +1,6 @@
# List of known limitations
## OpenSSL incompatibilities
- Because of a bug in OpenSSL 3.0.3 and below, the ``-dateopt`` argument is broken.
Since we require the attribute to read the timestamps in a predictable manner, these versions are unsupported for now.
- Known affected: [Ubuntu 22](https://launchpad.net/ubuntu/jammy/+package/openssl)

View file

@ -24,9 +24,6 @@ public class Certificate {
@Enumerated(EnumType.STRING)
private CertificateType type;
@NotNull
private String commonName;
private String trustingAuthority;
@Min(1)
@ -35,6 +32,9 @@ public class Certificate {
@Min(1)
private int requestedValidityDays;
@NotNull
private String subjectCommonName;
private String subjectEmailAddress;
private String subjectOrganization;
private String subjectOrganizationalUnit;
@ -45,15 +45,15 @@ public class Certificate {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<CertificateExtension> certificateExtension = new ArrayList<>();
@Lob
//@Lob - Cannot annotate column: https://github.com/xerial/sqlite-jdbc/issues/135
@Column(nullable = false)
private byte[] cert = new byte[0];
@Lob
//@Lob - Cannot annotate column: https://github.com/xerial/sqlite-jdbc/issues/135
@Column(nullable = false)
private byte[] privateKey = new byte[0];
@Lob
//@Lob - Cannot annotate column: https://github.com/xerial/sqlite-jdbc/issues/135
@Column
private byte[] fullchain;

View file

@ -8,7 +8,6 @@ import lombok.Data;
public class CertificateRequest {
private RequestType type;
private String commonName;
private String trustingAuthority;
@Builder.Default
@ -18,6 +17,7 @@ public class CertificateRequest {
private int requestedValidityDays = 365;
private CertificateSubject subject;
private CertificateSubject issuer;
private CertificateRequestExtension extension;
public enum RequestType {
@ -33,6 +33,11 @@ public class CertificateRequest {
return this;
}
public CertificateRequestBuilder issuer(CertificateSubject.CertificateSubjectBuilder builder) {
this.issuer = builder.build();
return this;
}
public CertificateRequestBuilder extension(
CertificateRequestExtension.CertificateRequestExtensionBuilder builder
) {

View file

@ -1,6 +1,8 @@
package de.mlessmann.certassist.openssl;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import lombok.Builder;
import lombok.Getter;
@ -13,7 +15,14 @@ public class CertificateRequestExtension {
public static class CertificateRequestExtensionBuilder {
public CertificateRequestExtensionBuilder alternativeNames(String... altNames) {
this.alternativeNames = List.of(altNames);
Objects.requireNonNull(altNames, "Alternative names must not be null (but can be empty)");
this.alternativeNames =
Stream
.of(altNames)
.filter(Objects::nonNull)
.map(String::trim)
.map(name -> name.replaceAll("^DNS:", ""))
.toList();
return this;
}
}

View file

@ -7,10 +7,13 @@ import lombok.Getter;
@Builder
public class CertificateSubject {
private String commonName;
private String emailAddress;
private String organization;
private String organizationalUnit;
private String country;
private String state;
private String locality;
public static class CertificateSubjectBuilder {}
}

View file

@ -7,6 +7,7 @@ import de.mlessmann.certassist.ExecutableResolver;
import de.mlessmann.certassist.except.CommandLineOperationException;
import de.mlessmann.certassist.except.UnresolvableCLIDependency;
import de.mlessmann.certassist.openssl.CertificateRequest.RequestType;
import de.mlessmann.certassist.openssl.CertificateSubject.CertificateSubjectBuilder;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -15,6 +16,7 @@ import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -48,6 +50,7 @@ public class OpenSSLCertificateCreator {
private static final Pattern FINGERPRINT_EXTRACTOR = Pattern.compile(
"^(?<algo>[0-9a-zA-Z]+) (?i)Fingerprint(?-i)=(?<finger>[a-z:A-Z0-9]+)"
);
private final AtomicBoolean versionLogged = new AtomicBoolean(false);
private final ExecutableResolver executableResolver;
private final CertificatePasswordProvider passwordProvider;
@ -59,7 +62,7 @@ public class OpenSSLCertificateCreator {
.replace("STATE", request.getSubject().getState())
.replace("LOCALITY", request.getSubject().getLocality())
.replace("ORGANIZATION", request.getSubject().getOrganization())
.replace("COMMON-NAME", request.getCommonName());
.replace("COMMON-NAME", request.getSubject().getCommonName());
if (StringUtils.isNotBlank(request.getSubject().getOrganizationalUnit())) {
certSubject += "/OU=" + request.getSubject().getOrganizationalUnit();
@ -351,7 +354,7 @@ public class OpenSSLCertificateCreator {
return outFile;
}
private String getCertificateFingerprint(Path certificate)
public String getCertificateFingerprint(Path certificate)
throws CommandLineOperationException, InterruptedException {
try {
StartedProcess fingerprintProc = new ProcessExecutor()
@ -361,6 +364,16 @@ public class OpenSSLCertificateCreator {
.start();
var fingerprintResult = fingerprintProc.getFuture().get();
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);
@ -381,11 +394,130 @@ public class OpenSSLCertificateCreator {
}
}
public CertificateRequest getCertificateInfo(Path path) throws CommandLineOperationException, InterruptedException {
try {
StartedProcess 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.ofCaller().asError())
.start();
var infoResult = infoProc.getFuture().get();
String output = infoResult.getOutput().getUTF8();
if (infoResult.getExitValue() != 0) {
log.debug("Certificate info command output:\n{}", output);
throw new CommandLineOperationException(
"Failed to get info of path. Exit code: %d".formatted(infoResult.getExitValue())
);
}
return getCertificateInfo(output.lines().toArray(String[]::new));
} catch (IOException | ExecutionException e) {
throw new RuntimeException(e);
}
}
private String resolveOpenSSL() throws CommandLineOperationException {
try {
return executableResolver.getOpenSSLPath();
String path = executableResolver.getOpenSSLPath();
if (!versionLogged.get()) {
try {
StartedProcess versionProc = new ProcessExecutor()
.command(path, "version")
.readOutput(true)
.redirectError(Slf4jStream.ofCaller().asError())
.start();
var versionResult = versionProc.getFuture().get();
if (versionResult.getExitValue() != 0) {
throw new CommandLineOperationException(
"Failed to get OpenSSL version. Exit code: " + versionResult.getExitValue()
);
}
log.info("Using OpenSSL version: {}", versionResult.getOutput().getUTF8());
versionLogged.set(true);
} catch (IOException | InterruptedException | ExecutionException e) {
throw new CommandLineOperationException("Failed to get OpenSSL version", e);
}
}
return path;
} catch (UnresolvableCLIDependency e) {
throw new CommandLineOperationException(e);
}
}
private CertificateRequest getCertificateInfo(String[] lines) {
var builder = CertificateRequest.builder();
boolean hasIssuer = false;
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
if (line.startsWith("subject=")) {
CertificateSubjectBuilder subjectBuilder = CertificateSubject.builder();
line = lines[++i];
while (line.startsWith(" ")) {
subjectBuilder = readSubjectInfo(line, subjectBuilder);
line = lines[++i];
}
builder = builder.subject(subjectBuilder);
} else if (line.startsWith("issuer=")) {
hasIssuer = true;
CertificateSubjectBuilder issuerBuilder = CertificateSubject.builder();
line = lines[++i];
while (line.startsWith(" ")) {
issuerBuilder = readSubjectInfo(line, issuerBuilder);
line = lines[++i];
}
builder = builder.issuer(issuerBuilder);
} else if (line.startsWith("X509v3 Subject Alternative Name")) {
String[] altNames = lines[++i].split(",");
builder = builder.extension(CertificateRequestExtension.builder().alternativeNames(altNames));
}
}
builder = builder.type(hasIssuer ? RequestType.NORMAL_CERTIFICATE : RequestType.STANDALONE_CERTIFICATE);
return builder.build();
}
private CertificateSubjectBuilder readSubjectInfo(String line, CertificateSubjectBuilder builder) {
String[] parts = line.split("=", 2);
if (parts.length != 2) {
return builder;
}
String key = parts[0];
String value = parts[1];
return switch (key.trim()) {
case "countryName" -> builder.country(value);
case "stateOrProvinceName" -> builder.state(value);
case "localityName" -> builder.locality(value);
case "organizationName" -> builder.organization(value);
case "organizationalUnitName" -> builder.organizationalUnit(value);
case "commonName" -> builder.commonName(value);
case "emailAddress" -> builder.emailAddress(value);
default -> throw new IllegalStateException("Unexpected subject key: %s in line: %s".formatted(key, line));
};
}
}

View file

@ -8,8 +8,10 @@ import de.mlessmann.certassist.openssl.*;
import de.mlessmann.certassist.repositories.CertificateRepository;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@Service
@ -18,29 +20,10 @@ public class CertificateCreationService {
private final CertificateRepository certificateRepository;
private final OpenSSLCertificateCreator openSSLCertificateCreator;
private final PassphraseService passphraseService;
public Certificate createCertificate(final CertificateRequest certificateRequest) {
final Certificate certificate = new Certificate();
certificate.setType(mapCertificateRequestType(certificateRequest.getType()));
certificate.setCommonName(certificateRequest.getCommonName());
certificate.setTrustingAuthority(certificateRequest.getTrustingAuthority());
certificate.setRequestedKeyLength(certificateRequest.getRequestedKeyLength());
certificate.setRequestedValidityDays(certificateRequest.getRequestedValidityDays());
final CertificateSubject certificateSubject = certificateRequest.getSubject();
certificate.setSubjectEmailAddress(certificateSubject.getEmailAddress());
certificate.setSubjectOrganization(certificateSubject.getOrganization());
certificate.setSubjectOrganizationalUnit(certificateSubject.getOrganizationalUnit());
certificate.setSubjectCountry(certificateSubject.getCountry());
certificate.setSubjectState(certificateSubject.getState());
certificate.setSubjectLocality(certificateSubject.getLocality());
final CertificateRequestExtension extension = certificateRequest.getExtension();
if (extension != null) {
final CertificateExtension certificateExtension = new CertificateExtension();
certificateExtension.setIdentifier("alternativeNames");
certificateExtension.setValue(String.join(",", extension.getAlternativeNames()));
certificate.setCertificateExtension(List.of(certificateExtension));
}
final Certificate certificate = createEntityFromRequest(certificateRequest);
try (
OpenSSLCertificateResult certificateCreatorResult = openSSLCertificateCreator.createCertificate(
@ -60,6 +43,49 @@ public class CertificateCreationService {
return certificate;
}
private Certificate createEntityFromRequest(CertificateRequest certificateRequest) {
final Certificate certificate = new Certificate();
certificate.setType(mapCertificateRequestType(certificateRequest.getType()));
certificate.setSubjectCommonName(certificateRequest.getSubject().getCommonName());
certificate.setTrustingAuthority(certificateRequest.getTrustingAuthority());
certificate.setRequestedKeyLength(certificateRequest.getRequestedKeyLength());
certificate.setRequestedValidityDays(certificateRequest.getRequestedValidityDays());
final CertificateSubject certificateSubject = certificateRequest.getSubject();
certificate.setSubjectEmailAddress(certificateSubject.getEmailAddress());
certificate.setSubjectOrganization(certificateSubject.getOrganization());
certificate.setSubjectOrganizationalUnit(certificateSubject.getOrganizationalUnit());
certificate.setSubjectCountry(certificateSubject.getCountry());
certificate.setSubjectState(certificateSubject.getState());
certificate.setSubjectLocality(certificateSubject.getLocality());
final CertificateRequestExtension extension = certificateRequest.getExtension();
if (extension != null) {
final CertificateExtension certificateExtension = new CertificateExtension();
certificateExtension.setIdentifier("alternativeNames");
certificateExtension.setValue(String.join(",", extension.getAlternativeNames()));
certificate.setCertificateExtension(List.of(certificateExtension));
}
return certificate;
}
public Certificate importCertificate(Path certificate, Path keyFile, String passphrase) {
try {
String fingerprint = openSSLCertificateCreator.getCertificateFingerprint(certificate);
var generatedRequest = openSSLCertificateCreator.getCertificateInfo(certificate);
Certificate entity = createEntityFromRequest(generatedRequest);
entity.setCert(Files.readAllBytes(certificate));
entity.setPrivateKey(Files.readAllBytes(keyFile));
if (StringUtils.isNotBlank(passphrase)) {
passphraseService.storePassphrase("cert:" + fingerprint, passphrase);
}
return certificateRepository.save(entity);
} catch (CommandLineOperationException | IOException e) {
throw new RuntimeException("Unable to import certificate", e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private CertificateType mapCertificateRequestType(CertificateRequest.RequestType requestType) {
return switch (requestType) {
case ROOT_AUTHORITY -> CertificateType.ROOT_CA;

View file

@ -9,7 +9,10 @@ spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
#TODO: Use flyway for db setup
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
hibernate.format_sql=true
# Logging
logging.level.root=INFO
logging.level.de.mlessmann.certassist=DEBUG
logging.level.org.sqlite=TRACE
logging.level.org.hibernate=DEBUG

View file

@ -0,0 +1,52 @@
package de.mlessmann.certassist;
import static org.assertj.core.api.Assertions.assertThat;
import de.mlessmann.certassist.openssl.CertificateRequest;
import de.mlessmann.certassist.openssl.CertificateRequestExtension;
import de.mlessmann.certassist.openssl.CertificateSubject;
import de.mlessmann.certassist.service.CertificateCreationService;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class CertificateServiceTest {
@Autowired
private CertificateCreationService certificateService;
@Test
void testCanCreateCertificate() {
var request = CertificateRequest
.builder()
.type(CertificateRequest.RequestType.STANDALONE_CERTIFICATE)
.subject(
CertificateSubject
.builder()
.commonName("cert.creation")
.country("DE")
.state("SH")
.locality("HH")
.organization("Crazy-Cats")
)
.extension(CertificateRequestExtension.builder().alternativeNames("test2.home", "test3.home"))
.build();
var cert = certificateService.createCertificate(request);
assertThat(cert).isNotNull();
assertThat(cert.getId()).isGreaterThan("0");
}
@Test
void testCanImportCertificate() {
Path certDir = TestOpenSSLCertificateCreator.TEST_CERT_PATH;
var importedCert = certificateService.importCertificate(
certDir.resolve("x509forImport.pem"),
certDir.resolve("x509forImport.key.pem"),
TestOpenSSLCertificateCreator.TEST_CERT_PASSPHRASE
);
assertThat(importedCert).isNotNull();
assertThat(importedCert.getId()).isGreaterThan("0");
}
}

View file

@ -12,12 +12,15 @@ import org.junit.jupiter.api.Test;
class TestOpenSSLCertificateCreator {
public static final String TEST_CERT_PASSPHRASE = "ABC-123";
public static final Path TEST_CERT_PATH = Path.of("src/test/resources/openssl");
private CertificatePasswordProvider passwordProvider;
@BeforeEach
void setUp() {
passwordProvider = mock(CertificatePasswordProvider.class);
when(passwordProvider.generateNewPassword()).thenReturn("ABC-123");
when(passwordProvider.generateNewPassword()).thenReturn(TEST_CERT_PASSPHRASE);
when(passwordProvider.getPasswordFor(anyString())).thenReturn(TEST_CERT_PASSPHRASE);
}
@Test
@ -32,9 +35,16 @@ class TestOpenSSLCertificateCreator {
CertificateRequest certRequest = CertificateRequest
.builder()
.commonName("test.home")
.type(RequestType.STANDALONE_CERTIFICATE)
.subject(CertificateSubject.builder().country("DE").state("SH").locality("HH").organization("Crazy-Cats"))
.subject(
CertificateSubject
.builder()
.commonName("test.home")
.country("DE")
.state("SH")
.locality("HH")
.organization("Crazy-Cats")
)
.extension(CertificateRequestExtension.builder().alternativeNames("test2.home", "test3.home"))
.build();
@ -45,12 +55,18 @@ class TestOpenSSLCertificateCreator {
CertificateRequest childRequest = CertificateRequest
.builder()
.commonName("test.local")
.type(RequestType.NORMAL_CERTIFICATE)
.trustingAuthority(cert.fingerprint())
.subject(
CertificateSubject.builder().country("DE").state("SH").locality("HH").organization("Crazy-Cats")
CertificateSubject
.builder()
.commonName("test.local")
.country("DE")
.state("SH")
.locality("HH")
.organization("Crazy-Cats")
)
.extension(CertificateRequestExtension.builder().alternativeNames("test2.local", "test3.local"))
.build();
var spiedCert = spy(cert);
@ -66,4 +82,33 @@ class TestOpenSSLCertificateCreator {
}
}
}
@Test
void testCertificateImport() throws Exception {
CertificateProvider certificateProvider = mock(CertificateProvider.class);
ExecutableResolver executableResolver = new ExecutableResolver();
var certificateCreator = new OpenSSLCertificateCreator(
executableResolver,
passwordProvider,
certificateProvider
);
var request = certificateCreator.getCertificateInfo(TEST_CERT_PATH.resolve("x509forImportCA.pem"));
assertThat(request).isNotNull();
assertThat(request.getSubject().getCommonName()).isEqualTo("test.home");
assertThat(request.getSubject().getCountry()).isEqualTo("DE");
assertThat(request.getSubject().getState()).isEqualTo("SH");
assertThat(request.getSubject().getLocality()).isEqualTo("HH");
assertThat(request.getSubject().getOrganization()).isEqualTo("Crazy-Cats");
assertThat(request.getExtension()).isNull();
request = certificateCreator.getCertificateInfo(TEST_CERT_PATH.resolve("x509forImport.pem"));
assertThat(request).isNotNull();
assertThat(request.getSubject().getCommonName()).isEqualTo("test.local");
assertThat(request.getSubject().getCountry()).isEqualTo("DE");
assertThat(request.getSubject().getState()).isEqualTo("SH");
assertThat(request.getSubject().getLocality()).isEqualTo("HH");
assertThat(request.getSubject().getOrganization()).isEqualTo("Crazy-Cats");
assertThat(request.getExtension().getAlternativeNames()).containsExactly("test2.local", "test3.local");
}
}

View file

@ -7,6 +7,8 @@ import de.mlessmann.certassist.models.CertificateExtension;
import de.mlessmann.certassist.models.CertificateType;
import jakarta.transaction.Transactional;
import java.util.List;
import java.util.stream.StreamSupport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@ -20,9 +22,15 @@ class CertificateRepositoryTest {
@Autowired
private CertificateExtensionRepository extensionRepository;
@BeforeEach
torge-hmn commented 2024-11-22 23:48:09 +00:00 (Migrated from github.com)
Review

Die Bulk-Operation ist kein Problem. Es scheint ein Problem beim Column-Mapping zu geben. Ich habe schon ein paar Sachen recherchiert und ausprobiert, aber bekomme es nicht gefixt. Interessanter Weise ist der Test in der IDE-Ausführung immer grün, auch mit dem Test-Profil. Es bricht nur der Gradle-Test ab, was ich auch lokal reproduzieren kann. Interessanter Weise kommt es gelegentlich zu einer erfolgreichen Test-Ausführung, sodass vermutlich irgendwo noch ein Reihenfolge oder Caching-Problem vorliegt.

Die Bulk-Operation ist kein Problem. Es scheint ein Problem beim Column-Mapping zu geben. Ich habe schon ein paar Sachen recherchiert und ausprobiert, aber bekomme es nicht gefixt. Interessanter Weise ist der Test in der IDE-Ausführung immer grün, auch mit dem Test-Profil. Es bricht nur der Gradle-Test ab, was ich auch lokal reproduzieren kann. Interessanter Weise kommt es gelegentlich zu einer erfolgreichen Test-Ausführung, sodass vermutlich irgendwo noch ein Reihenfolge oder Caching-Problem vorliegt.
MarkL4YG commented 2024-11-23 09:48:21 +00:00 (Migrated from github.com)
Review

Interessant. Ich werde mir das noch weiter anschauen. Die Bulk-Operation war halt in der Zeile, die in einer der Exceptions aufgetaucht ist. Aber dann war das wohl etwas unterliegendes.
Jedenfalls zeigt sich einmal mehr, dass das Logging noch unzureichend ist. 😅

Interessant. Ich werde mir das noch weiter anschauen. Die Bulk-Operation war halt in der Zeile, die in einer der Exceptions aufgetaucht ist. Aber dann war das wohl etwas unterliegendes. Jedenfalls zeigt sich einmal mehr, dass das Logging noch unzureichend ist. 😅
MarkL4YG commented 2024-11-23 10:42:25 +00:00 (Migrated from github.com)
Review

Also das Hibernate-Problem scheint zu sein, dass er die BLOB-Felder über ResultSet#getBlob lesen will.
Die Methode ist vom SQLite Driver aber nicht implementiert und wirft daher einen Fehler.

Also das Hibernate-Problem scheint zu sein, dass er die BLOB-Felder über ``ResultSet#getBlob`` lesen will. Die Methode ist vom SQLite Driver aber nicht implementiert und wirft daher einen Fehler.
void cleanUp() {
extensionRepository.deleteAll();
repository.deleteAll();
}
private Certificate getCertificate() {
final Certificate certificate = new Certificate();
certificate.setCommonName("test-cn");
certificate.setSubjectCommonName("test-cn");
certificate.setType(CertificateType.SIGNED_CERT);
certificate.setRequestedKeyLength(1);
certificate.setRequestedValidityDays(1);
@ -37,7 +45,7 @@ class CertificateRepositoryTest {
repository.save(certificate);
Certificate foundCertificate = repository.findById(certificate.getId()).orElseThrow();
assertThat(foundCertificate.getCommonName()).isEqualTo("test-cn");
assertThat(foundCertificate.getSubjectCommonName()).isEqualTo("test-cn");
assertThat(foundCertificate.getType()).isEqualTo(CertificateType.SIGNED_CERT);
}
@ -51,8 +59,10 @@ class CertificateRepositoryTest {
repository.save(certificate);
assertThat(repository.findById(certificate.getId()).orElseThrow().getCertificateExtension()).hasSize(1);
assertThat(extensionRepository.findAll())
.singleElement()
.satisfies(ce -> assertThat(ce.getValue()).isEqualTo("test-ext-value"));
List<CertificateExtension> extensions = StreamSupport
.stream(extensionRepository.findAll().spliterator(), false)
.toList();
assertThat(extensions).hasSize(1);
assertThat(extensions).singleElement().satisfies(ce -> assertThat(ce.getValue()).isEqualTo("test-ext-value"));
}
}

View file

@ -0,0 +1,65 @@
-----BEGIN CERTIFICATE-----
MIIFgTCCA2mgAwIBAgIUVTm2kFBiacDG3Om6JFaZKvJ6CScwDQYJKoZIhvcNAQEL
BQAwUDELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNIMQswCQYDVQQHDAJISDETMBEG
A1UECgwKQ3JhenktQ2F0czESMBAGA1UEAwwJdGVzdC5ob21lMB4XDTI0MTEyMjE4
NTc0MFoXDTI1MTEyMjE4NTc0MFowUDELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNI
MQswCQYDVQQHDAJISDETMBEGA1UECgwKQ3JhenktQ2F0czESMBAGA1UEAwwJdGVz
dC5ob21lMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnnYaTE8TKKTu
IE2hcHwHm0RnM+4VwPnNT6q6b4oYSJJeGJCbQYt8CAAkBxvY3j1H0xP6imD7ULUK
ymx2fQiHK+bRAdXyoguLHaPYWPInfUyHp9Y2w2AglKCG/U2paMni4xL0LH4N3V1r
x5hQG9ORwJzVH5wMiYoETzgbd1ED7G0tVuKYrH84Ma8znEXVZ4XfAlDfYEGKPNDN
dZDkFEQFYHb5RSPB6ym3vrbZJfLkNy01m/Cpdj3/GqJ460zo7x/apzVPNj/khW1v
fME8c8sz1LqEbQBVdUU9xN8DfTjT/z1NHA5S/1O7Yb9Z0t1tjvs3u/iyCjBNMbd5
7FNqMVjXFjENav21zrrr0LAiUfBcbokQZD72/j053gXz9LkcTjLvkPN3i1oVfNIA
b/Ce4hHzpWk2kvZuFyqfKj8Yc8oTfjr88sGxe1JCgbhnCvkw/5d7hy7OJVG4mTIH
WliOY92R7xPwEBhqc+A4VYqhAIrp+Qzen+XGrBih+PneEsSWVfM3PyjKt/qURK4j
gWCGFIEI80xyFaHhwHpeKszZcMOVAHjV3Ik91wutL/HoZ0r5uPYvwqQb6QZ7ubqX
FWiuNUf970TFS7eIA33Xr0IojPBziinU3/uYnJBODR4Sl2npijzwqsRrGsrSDUF7
cxxhsyv64ri2p3u4wnP4faLf1Dtk3kUCAwEAAaNTMFEwHQYDVR0OBBYEFICs29k3
J5Pnza2xgkgycaxBxAcSMB8GA1UdIwQYMBaAFICs29k3J5Pnza2xgkgycaxBxAcS
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFiqNg1yXlajI1ZJ
l4qXADvi1xpFLEn17162jN+zb6DgRRohqwrsP71fAqyuCticYELk3gBHt8KizEBj
xPSggahX+ZIGG5/Tnb5cQ61y0GMZHWyABHjgiOGi26Gxar5wi7ET6W4D6w09u8U3
wd8q8U164aSj6Rh153S0r8SJJOhqtiZhcYllOMB8SNgjxodlco+YvCGoqkhPdJef
H2S02mgbjxMlo8P4ivoirD7boLirlXkoNidmaWvC/hD4ZwsTWMrRnHL1S5DEhKef
WSgtX3tUEvHrEyqsGcSn49l4CNE6Xbx5wWo+4c9bs642f7u34OoUnitYmGZDzT1d
zQOyBirn9k6fB/SX7Ug+PF6+KbJCXEdWffgNqe5HAG8Lo2EOEnocp3kncRMSq3zY
qzbTBqaWI512cqN2RA+Q0NPzVH01jPG8yKaSxVzf4Sp1Iqjl7fe9WZL9/L4DJZ7o
QWRPZvwH5Rz8utGUylfe9LxSisX7xZMNoGLqsQfanowqZCiS1M1GRoHsILnFuFNw
3ONu4qp0Gr3+PxsKoE6NBstqD6Lkrm0uf/IhC7fwIbdq3qqe0E4xZou9W9nkmapw
0iKhdhCtnp3HsF6f2Kc0ipyPxVXDnP5TCx4NLst2RvoYryUBwqIKfwU8X3yOBmhq
HowJo/ZfrlzZf8753cfF8Kza6NUA
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFrjCCA5agAwIBAgIUMhyzffHXKaQnfX/ckbx3KZ9AVXswDQYJKoZIhvcNAQEL
BQAwUDELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNIMQswCQYDVQQHDAJISDETMBEG
A1UECgwKQ3JhenktQ2F0czESMBAGA1UEAwwJdGVzdC5ob21lMB4XDTI0MTEyMjE4
NTc0M1oXDTI1MTEyMjE4NTc0M1owUTELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNI
MQswCQYDVQQHDAJISDETMBEGA1UECgwKQ3JhenktQ2F0czETMBEGA1UEAwwKdGVz
dC5sb2NhbDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALXcSLHkvB99
OBpf57gJTYwppQrsV1yUWJp1qKXCD164GlIul0YnBRLaaxZUfWIJaxEaWz5DFR4B
zR/MlaxUKx0A7fASyRNVA4NMGclt1WJR5fglHih8p+SIE1feVkMdeUftJnEJXVY1
DlZ9Cw7+nrkAmh5s8QEcVgak4kB4/6sFcMhrfa6tNcnhuM7Yq3USZf2a9CDpj78m
8NvIlXN4+bk9fgCSN9vOLzRuMMYT8mRy0fqXdNQcEE0i+SJ6epbsRvXmRdugd3a8
aaLE2Bc5huyaoc//ZneJY5iVyTldSo3eyj88/NvdhApExzeLb8T9Hey6DP/chDfu
KKASiisRL9kPKB++PTt3Suxy+Ary+MJ1uLJLIberb3qWFYyU7i1o3Ex7mBFIpCdW
SXw321cNYapoxMApeyMUWxn9j8GuHRAcARj2FDKIQasY6AqmQ8vL+MGFLqKaxqcD
QDu+8le4EUwNGxkVIINTYzZttsQF2zJ/DPhErbT0rz5TUn5TKzHeReUv+lFhre7E
2bIcocTumbxBuBMNE+1b09IPmdPZCMS8bRd4vQ+4QRHweyFHCHOQHjWN10uLpZhm
Mugq9spxIatQ4jsmuQHSM76f91CH6BvVx+gUNaMcFLkDjPUyrn6gov6SbcBQMuUO
gw4H4moFF+mtiivhvnWYWXRvlqPd9bR9AgMBAAGjfzB9MB8GA1UdIwQYMBaAFICs
29k3J5Pnza2xgkgycaxBxAcSMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMCMGA1Ud
EQQcMBqCC3Rlc3QyLmxvY2Fsggt0ZXN0My5sb2NhbDAdBgNVHQ4EFgQUr4a7BDH8
d4ZlrR3qcOMvczkWEqAwDQYJKoZIhvcNAQELBQADggIBAEwk+wOrXsYbPl+3IgJi
izJAapA5BI/DoX5KUxRgXByNEZeIYlmn0GAj2odxxV8+AnjsIFQGp+76e/s6LpJG
ImipyyZ+XCXSqYoyEaUn0KaJPMN0GQbOv/a/kM0BEYUGLkaJLzlGS3Hg0hhqhlYi
dx6xr92rZWW7HGSliv3QghGsXmQYBJ8rSq9HEpv/RAIm95H6BHltQ1FBMRssDnBB
Ur+WTQTzj5gVqxVKvPOzXxc+V3cs9Nozk+wzuW6KUiBPcfQcxdnlm5wToj/k75i9
iRW5E2Qqe+skmLQDxiTOJmgoZxtjGtVMC20J/XYF/mBEJqr+bND1W/8Tp/pUKzpF
z4SIzP/m2TpPH8Agnvie0Z6nVYgAZpiuBEd7JrPm0kWtuKgL3ER+67M/PjpYYmNx
A/gjmQgz7+7uAk8PhloWKbBOC/2wxk0jkFazRBk6e9RveUR6PBvBpo+nyQSgDQkD
dGyzvIAYcFTFXznSv8cu9wzizwGXJ0EdxSo+oWcsGzq3rhjzD+tROCqS7X1eVF4j
USrygPv8w4UT3SFoiABp1ThDjCheROLISAo/SIag8XUw58bmmiDRuVPLZDQfYBYu
KgSLJpFB99QMzvhkNBhImEpmWRrKAFSmwNG4/l/yR19udd4AxpjH62LT8mUZOf23
dQ6pRi1hNlM1v6ZvFDdq8Rgw
-----END CERTIFICATE-----

View file

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
BuqnHJNiSeD/vUZiKMmb5KHHBTQHQw==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFrjCCA5agAwIBAgIUMhyzffHXKaQnfX/ckbx3KZ9AVXswDQYJKoZIhvcNAQEL
BQAwUDELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNIMQswCQYDVQQHDAJISDETMBEG
A1UECgwKQ3JhenktQ2F0czESMBAGA1UEAwwJdGVzdC5ob21lMB4XDTI0MTEyMjE4
NTc0M1oXDTI1MTEyMjE4NTc0M1owUTELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNI
MQswCQYDVQQHDAJISDETMBEGA1UECgwKQ3JhenktQ2F0czETMBEGA1UEAwwKdGVz
dC5sb2NhbDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALXcSLHkvB99
OBpf57gJTYwppQrsV1yUWJp1qKXCD164GlIul0YnBRLaaxZUfWIJaxEaWz5DFR4B
zR/MlaxUKx0A7fASyRNVA4NMGclt1WJR5fglHih8p+SIE1feVkMdeUftJnEJXVY1
DlZ9Cw7+nrkAmh5s8QEcVgak4kB4/6sFcMhrfa6tNcnhuM7Yq3USZf2a9CDpj78m
8NvIlXN4+bk9fgCSN9vOLzRuMMYT8mRy0fqXdNQcEE0i+SJ6epbsRvXmRdugd3a8
aaLE2Bc5huyaoc//ZneJY5iVyTldSo3eyj88/NvdhApExzeLb8T9Hey6DP/chDfu
KKASiisRL9kPKB++PTt3Suxy+Ary+MJ1uLJLIberb3qWFYyU7i1o3Ex7mBFIpCdW
SXw321cNYapoxMApeyMUWxn9j8GuHRAcARj2FDKIQasY6AqmQ8vL+MGFLqKaxqcD
QDu+8le4EUwNGxkVIINTYzZttsQF2zJ/DPhErbT0rz5TUn5TKzHeReUv+lFhre7E
2bIcocTumbxBuBMNE+1b09IPmdPZCMS8bRd4vQ+4QRHweyFHCHOQHjWN10uLpZhm
Mugq9spxIatQ4jsmuQHSM76f91CH6BvVx+gUNaMcFLkDjPUyrn6gov6SbcBQMuUO
gw4H4moFF+mtiivhvnWYWXRvlqPd9bR9AgMBAAGjfzB9MB8GA1UdIwQYMBaAFICs
29k3J5Pnza2xgkgycaxBxAcSMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMCMGA1Ud
EQQcMBqCC3Rlc3QyLmxvY2Fsggt0ZXN0My5sb2NhbDAdBgNVHQ4EFgQUr4a7BDH8
d4ZlrR3qcOMvczkWEqAwDQYJKoZIhvcNAQELBQADggIBAEwk+wOrXsYbPl+3IgJi
izJAapA5BI/DoX5KUxRgXByNEZeIYlmn0GAj2odxxV8+AnjsIFQGp+76e/s6LpJG
ImipyyZ+XCXSqYoyEaUn0KaJPMN0GQbOv/a/kM0BEYUGLkaJLzlGS3Hg0hhqhlYi
dx6xr92rZWW7HGSliv3QghGsXmQYBJ8rSq9HEpv/RAIm95H6BHltQ1FBMRssDnBB
Ur+WTQTzj5gVqxVKvPOzXxc+V3cs9Nozk+wzuW6KUiBPcfQcxdnlm5wToj/k75i9
iRW5E2Qqe+skmLQDxiTOJmgoZxtjGtVMC20J/XYF/mBEJqr+bND1W/8Tp/pUKzpF
z4SIzP/m2TpPH8Agnvie0Z6nVYgAZpiuBEd7JrPm0kWtuKgL3ER+67M/PjpYYmNx
A/gjmQgz7+7uAk8PhloWKbBOC/2wxk0jkFazRBk6e9RveUR6PBvBpo+nyQSgDQkD
dGyzvIAYcFTFXznSv8cu9wzizwGXJ0EdxSo+oWcsGzq3rhjzD+tROCqS7X1eVF4j
USrygPv8w4UT3SFoiABp1ThDjCheROLISAo/SIag8XUw58bmmiDRuVPLZDQfYBYu
KgSLJpFB99QMzvhkNBhImEpmWRrKAFSmwNG4/l/yR19udd4AxpjH62LT8mUZOf23
dQ6pRi1hNlM1v6ZvFDdq8Rgw
-----END CERTIFICATE-----

View file

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
uG1j1Nb+oUNaeAMG8+ojFs1tLXHtStI=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFgTCCA2mgAwIBAgIUVTm2kFBiacDG3Om6JFaZKvJ6CScwDQYJKoZIhvcNAQEL
BQAwUDELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNIMQswCQYDVQQHDAJISDETMBEG
A1UECgwKQ3JhenktQ2F0czESMBAGA1UEAwwJdGVzdC5ob21lMB4XDTI0MTEyMjE4
NTc0MFoXDTI1MTEyMjE4NTc0MFowUDELMAkGA1UEBhMCREUxCzAJBgNVBAgMAlNI
MQswCQYDVQQHDAJISDETMBEGA1UECgwKQ3JhenktQ2F0czESMBAGA1UEAwwJdGVz
dC5ob21lMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnnYaTE8TKKTu
IE2hcHwHm0RnM+4VwPnNT6q6b4oYSJJeGJCbQYt8CAAkBxvY3j1H0xP6imD7ULUK
ymx2fQiHK+bRAdXyoguLHaPYWPInfUyHp9Y2w2AglKCG/U2paMni4xL0LH4N3V1r
x5hQG9ORwJzVH5wMiYoETzgbd1ED7G0tVuKYrH84Ma8znEXVZ4XfAlDfYEGKPNDN
dZDkFEQFYHb5RSPB6ym3vrbZJfLkNy01m/Cpdj3/GqJ460zo7x/apzVPNj/khW1v
fME8c8sz1LqEbQBVdUU9xN8DfTjT/z1NHA5S/1O7Yb9Z0t1tjvs3u/iyCjBNMbd5
7FNqMVjXFjENav21zrrr0LAiUfBcbokQZD72/j053gXz9LkcTjLvkPN3i1oVfNIA
b/Ce4hHzpWk2kvZuFyqfKj8Yc8oTfjr88sGxe1JCgbhnCvkw/5d7hy7OJVG4mTIH
WliOY92R7xPwEBhqc+A4VYqhAIrp+Qzen+XGrBih+PneEsSWVfM3PyjKt/qURK4j
gWCGFIEI80xyFaHhwHpeKszZcMOVAHjV3Ik91wutL/HoZ0r5uPYvwqQb6QZ7ubqX
FWiuNUf970TFS7eIA33Xr0IojPBziinU3/uYnJBODR4Sl2npijzwqsRrGsrSDUF7
cxxhsyv64ri2p3u4wnP4faLf1Dtk3kUCAwEAAaNTMFEwHQYDVR0OBBYEFICs29k3
J5Pnza2xgkgycaxBxAcSMB8GA1UdIwQYMBaAFICs29k3J5Pnza2xgkgycaxBxAcS
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFiqNg1yXlajI1ZJ
l4qXADvi1xpFLEn17162jN+zb6DgRRohqwrsP71fAqyuCticYELk3gBHt8KizEBj
xPSggahX+ZIGG5/Tnb5cQ61y0GMZHWyABHjgiOGi26Gxar5wi7ET6W4D6w09u8U3
wd8q8U164aSj6Rh153S0r8SJJOhqtiZhcYllOMB8SNgjxodlco+YvCGoqkhPdJef
H2S02mgbjxMlo8P4ivoirD7boLirlXkoNidmaWvC/hD4ZwsTWMrRnHL1S5DEhKef
WSgtX3tUEvHrEyqsGcSn49l4CNE6Xbx5wWo+4c9bs642f7u34OoUnitYmGZDzT1d
zQOyBirn9k6fB/SX7Ug+PF6+KbJCXEdWffgNqe5HAG8Lo2EOEnocp3kncRMSq3zY
qzbTBqaWI512cqN2RA+Q0NPzVH01jPG8yKaSxVzf4Sp1Iqjl7fe9WZL9/L4DJZ7o
QWRPZvwH5Rz8utGUylfe9LxSisX7xZMNoGLqsQfanowqZCiS1M1GRoHsILnFuFNw
3ONu4qp0Gr3+PxsKoE6NBstqD6Lkrm0uf/IhC7fwIbdq3qqe0E4xZou9W9nkmapw
0iKhdhCtnp3HsF6f2Kc0ipyPxVXDnP5TCx4NLst2RvoYryUBwqIKfwU8X3yOBmhq
HowJo/ZfrlzZf8753cfF8Kza6NUA
-----END CERTIFICATE-----

View file

@ -0,0 +1 @@
321CB37DF1D729A4277D7FDC91BC77299F40557B