feat: Implement new services required or OpenSSL certificate creation

This commit is contained in:
Magnus Leßmann (@MarkL4YG) 2024-11-22 08:40:26 +01:00
parent e5f0dc4a69
commit 6005e0a479
Signed by: Mark.TwoFive
GPG key ID: 5B5EBCBE331F1E6F
7 changed files with 128 additions and 6 deletions

View file

@ -0,0 +1,10 @@
package de.mlessmann.certassist;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Constants {
public static final String CERTASSIST_TMP_PREFIX = "certassist_";
}

View file

@ -1,6 +1,6 @@
package de.mlessmann.certassist.openssl;
public interface CertPasswordProvider {
public interface CertificatePasswordProvider {
String generateNewPassword();
String getPasswordFor(String certificateFingerprint);

View file

@ -1,5 +1,7 @@
package de.mlessmann.certassist.openssl;
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
import de.mlessmann.certassist.ExecutableResolver;
import de.mlessmann.certassist.except.CommandLineOperationException;
import de.mlessmann.certassist.except.UnresolvableCLIDependency;
@ -46,7 +48,7 @@ public class OpenSSLCertificateCreator {
);
private final ExecutableResolver executableResolver;
private final CertPasswordProvider passwordProvider;
private final CertificatePasswordProvider passwordProvider;
private final CertificateProvider certificateProvider;
private static String buildSubjectArg(CertificateRequest request) {
@ -72,7 +74,7 @@ public class OpenSSLCertificateCreator {
throws CommandLineOperationException, InterruptedException {
Path tmpDir;
try {
tmpDir = Files.createTempDirectory("certassist");
tmpDir = Files.createTempDirectory(CERTASSIST_TMP_PREFIX);
} catch (IOException e) {
throw new CommandLineOperationException("Could not create temporary directory for certificate creation", e);
}

View file

@ -5,4 +5,6 @@ import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CertificateRepository extends CrudRepository<Certificate, String> {}
public interface CertificateRepository extends CrudRepository<Certificate, String> {
Certificate findByFingerprintIs(String fingerprint);
}

View file

@ -0,0 +1,68 @@
package de.mlessmann.certassist.service;
import de.mlessmann.certassist.Constants;
import de.mlessmann.certassist.DeleteRecursiveFileVisitor;
import de.mlessmann.certassist.openssl.CertificateProvider;
import de.mlessmann.certassist.openssl.CertificateUsage;
import de.mlessmann.certassist.repositories.CertificateRepository;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@RequiredArgsConstructor
public class CertificateProviderImpl implements CertificateProvider {
private static final OpenOption[] CREATE_TRUNCATE = new OpenOption[] {
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
};
private final CertificateRepository certificateRepository;
@Override
public CertificateUsage requestCertificateUsage(String fingerprint) {
var certFromDB = certificateRepository.findByFingerprintIs(fingerprint);
if (certFromDB == null) {
throw new IllegalArgumentException("Unknown fingerprint");
}
try {
Path tempDirectory = Files.createTempDirectory(Constants.CERTASSIST_TMP_PREFIX);
Files.write(tempDirectory.resolve("key.pem"), certFromDB.getPrivateKey(), CREATE_TRUNCATE);
Files.write(tempDirectory.resolve("cert.pem"), certFromDB.getCert(), CREATE_TRUNCATE);
return new ExtractedCert(tempDirectory, certFromDB.getFingerprint());
} catch (IOException e) {
// TODO: Better exception definitions
throw new RuntimeException("Unable to temporarily store certificate for use.", e);
}
}
@Slf4j
private record ExtractedCert(Path tempDir, String fingerprint) implements CertificateUsage {
@Override
public Path certificateKeyPath() {
return this.tempDir.resolve("key.pem");
}
@Override
public Path certificatePath() {
return this.tempDir.resolve("cert.pem");
}
@Override
public void close() {
try {
Files.walkFileTree(this.tempDir, new DeleteRecursiveFileVisitor());
Files.deleteIfExists(this.tempDir);
} catch (IOException e) {
log.error("Unable to clean up temporary directory: " + this.tempDir, e);
}
}
}
}

View file

@ -10,11 +10,11 @@ import org.junit.jupiter.api.Test;
class TestOpenSSLCertificateCreator {
private CertPasswordProvider passwordProvider;
private CertificatePasswordProvider passwordProvider;
@BeforeEach
void setUp() {
passwordProvider = mock(CertPasswordProvider.class);
passwordProvider = mock(CertificatePasswordProvider.class);
when(passwordProvider.generateNewPassword()).thenReturn("ABC-123");
}

View file

@ -0,0 +1,40 @@
package de.mlessmann.certassist.service;
import de.mlessmann.certassist.openssl.CertificatePasswordProvider;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Service;
@Service
public class InMemoryCertificatePasswordProvider implements CertificatePasswordProvider {
private final Map<String, String> passwords = new ConcurrentHashMap<>();
private static final String CHARACTERS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,.<>?";
private static final int PASSPHRASE_LENGTH = 16;
@Override
public String generateNewPassword() {
SecureRandom random = new SecureRandom();
StringBuilder passphrase = new StringBuilder(PASSPHRASE_LENGTH);
for (int i = 0; i < PASSPHRASE_LENGTH; i++) {
passphrase.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));
}
return passphrase.toString();
}
@Override
public String getPasswordFor(String certificateFingerprint) {
return Optional.ofNullable(passwords.get(certificateFingerprint)).orElseThrow();
}
@Override
public void setPasswordFor(String certificateFingerprint, String password) {
Objects.requireNonNull(certificateFingerprint);
Objects.requireNonNull(password);
passwords.put(certificateFingerprint, password);
}
}