feat: Implement new services required or OpenSSL certificate creation
This commit is contained in:
parent
196d217bb7
commit
ac3821c949
7 changed files with 128 additions and 6 deletions
10
src/main/java/de/mlessmann/certassist/Constants.java
Normal file
10
src/main/java/de/mlessmann/certassist/Constants.java
Normal 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_";
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package de.mlessmann.certassist.openssl;
|
package de.mlessmann.certassist.openssl;
|
||||||
|
|
||||||
public interface CertPasswordProvider {
|
public interface CertificatePasswordProvider {
|
||||||
String generateNewPassword();
|
String generateNewPassword();
|
||||||
|
|
||||||
String getPasswordFor(String certificateFingerprint);
|
String getPasswordFor(String certificateFingerprint);
|
|
@ -1,5 +1,7 @@
|
||||||
package de.mlessmann.certassist.openssl;
|
package de.mlessmann.certassist.openssl;
|
||||||
|
|
||||||
|
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
|
||||||
|
|
||||||
import de.mlessmann.certassist.ExecutableResolver;
|
import de.mlessmann.certassist.ExecutableResolver;
|
||||||
import de.mlessmann.certassist.except.CommandLineOperationException;
|
import de.mlessmann.certassist.except.CommandLineOperationException;
|
||||||
import de.mlessmann.certassist.except.UnresolvableCLIDependency;
|
import de.mlessmann.certassist.except.UnresolvableCLIDependency;
|
||||||
|
@ -46,7 +48,7 @@ public class OpenSSLCertificateCreator {
|
||||||
);
|
);
|
||||||
|
|
||||||
private final ExecutableResolver executableResolver;
|
private final ExecutableResolver executableResolver;
|
||||||
private final CertPasswordProvider passwordProvider;
|
private final CertificatePasswordProvider passwordProvider;
|
||||||
private final CertificateProvider certificateProvider;
|
private final CertificateProvider certificateProvider;
|
||||||
|
|
||||||
private static String buildSubjectArg(CertificateRequest request) {
|
private static String buildSubjectArg(CertificateRequest request) {
|
||||||
|
@ -72,7 +74,7 @@ public class OpenSSLCertificateCreator {
|
||||||
throws CommandLineOperationException, InterruptedException {
|
throws CommandLineOperationException, InterruptedException {
|
||||||
Path tmpDir;
|
Path tmpDir;
|
||||||
try {
|
try {
|
||||||
tmpDir = Files.createTempDirectory("certassist");
|
tmpDir = Files.createTempDirectory(CERTASSIST_TMP_PREFIX);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new CommandLineOperationException("Could not create temporary directory for certificate creation", e);
|
throw new CommandLineOperationException("Could not create temporary directory for certificate creation", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,6 @@ import org.springframework.data.repository.CrudRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface CertificateRepository extends CrudRepository<Certificate, String> {}
|
public interface CertificateRepository extends CrudRepository<Certificate, String> {
|
||||||
|
Certificate findByFingerprintIs(String fingerprint);
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,11 +10,11 @@ import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class TestOpenSSLCertificateCreator {
|
class TestOpenSSLCertificateCreator {
|
||||||
|
|
||||||
private CertPasswordProvider passwordProvider;
|
private CertificatePasswordProvider passwordProvider;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
passwordProvider = mock(CertPasswordProvider.class);
|
passwordProvider = mock(CertificatePasswordProvider.class);
|
||||||
when(passwordProvider.generateNewPassword()).thenReturn("ABC-123");
|
when(passwordProvider.generateNewPassword()).thenReturn("ABC-123");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue