Introduction
Certificate management is crucial for secure communication in Java applications. It involves handling digital certificates, key stores, trust stores, and establishing secure TLS/SSL connections. Proper certificate management ensures authentication, encryption, and integrity in distributed systems.
Key Concepts
Digital Certificates
- X.509 Certificates - Standard format for public key certificates
- Public Key Infrastructure (PKI) - Framework for managing digital certificates
- Certificate Authorities (CA) - Entities that issue digital certificates
- KeyStore - Database for private keys and certificates
- TrustStore - Database for trusted CA certificates
Java Security Components
- KeyStore API - Management of cryptographic keys and certificates
- SSLContext - Factory for creating SSL sockets and engines
- TrustManager - Validation of certificate chains
- KeyManager - Management of client certificates
Setup and Dependencies
Maven Dependencies
<properties> <bouncycastle.version>1.70</bouncycastle.version> </properties> <dependencies> <!-- Bouncy Castle Crypto API --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>${bouncycastle.version}</version> </dependency> <!-- For PEM file handling --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>${bouncycastle.version}</version> </dependency> <!-- Apache Commons IO for file operations --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> </dependencies> Certificate Management Core Classes
1. Certificate Utility Class
package com.example.security.cert; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; import java.io.*; import java.math.BigInteger; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Date; public class CertificateUtils { static { Security.addProvider(new BouncyCastleProvider()); } private CertificateUtils() { // Utility class } public static KeyPair generateKeyPair(String algorithm, int keySize) throws NoSuchAlgorithmException, NoSuchProviderException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm, "BC"); keyGen.initialize(keySize); return keyGen.generateKeyPair(); } public static X509Certificate generateSelfSignedCertificate( KeyPair keyPair, String dn, Date startDate, Date endDate) throws Exception { X500Name subject = new X500Name(dn); BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( subject, serial, startDate, endDate, subject, keyPair.getPublic()); ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption") .setProvider("BC") .build(keyPair.getPrivate()); X509CertificateHolder certHolder = certBuilder.build(signer); return new JcaX509CertificateConverter() .setProvider("BC") .getCertificate(certHolder); } public static PKCS10CertificationRequest generateCertificateSigningRequest( KeyPair keyPair, String subjectDN) throws Exception { X500Name subject = new X500Name(subjectDN); PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic()); ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption") .setProvider("BC") .build(keyPair.getPrivate()); return requestBuilder.build(signer); } public static void saveCertificateToPEM(X509Certificate certificate, String filePath) throws IOException { try (JcaPEMWriter writer = new JcaPEMWriter(new FileWriter(filePath))) { writer.writeObject(certificate); } } public static X509Certificate loadCertificateFromPEM(String filePath) throws IOException, CertificateException { try (PEMParser parser = new PEMParser(new FileReader(filePath))) { Object object = parser.readObject(); if (object instanceof X509Certificate) { return (X509Certificate) object; } else if (object instanceof X509CertificateHolder) { return new JcaX509CertificateConverter() .setProvider("BC") .getCertificate((X509CertificateHolder) object); } throw new CertificateException("Invalid certificate format"); } } public static void savePrivateKeyToPEM(PrivateKey privateKey, String filePath) throws IOException { try (JcaPEMWriter writer = new JcaPEMWriter(new FileWriter(filePath))) { writer.writeObject(privateKey); } } public static boolean verifyCertificate(X509Certificate certificate, PublicKey publicKey) { try { certificate.verify(publicKey); return true; } catch (Exception e) { return false; } } } 2. KeyStore Manager
package com.example.security.keystore; import java.io.*; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Enumeration; public class KeyStoreManager { private final KeyStore keyStore; private final String keyStorePath; private final char[] keyStorePassword; public KeyStoreManager(String keyStorePath, char[] keyStorePassword, String type) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { this.keyStorePath = keyStorePath; this.keyStorePassword = keyStorePassword; this.keyStore = KeyStore.getInstance(type); File keyStoreFile = new File(keyStorePath); if (keyStoreFile.exists()) { try (FileInputStream fis = new FileInputStream(keyStoreFile)) { keyStore.load(fis, keyStorePassword); } } else { // Initialize empty keystore keyStore.load(null, keyStorePassword); } } public void addPrivateKeyEntry(String alias, PrivateKey privateKey, char[] keyPassword, Certificate[] certificateChain) throws KeyStoreException { keyStore.setKeyEntry(alias, privateKey, keyPassword, certificateChain); } public void addCertificateEntry(String alias, Certificate certificate) throws KeyStoreException { keyStore.setCertificateEntry(alias, certificate); } public Certificate getCertificate(String alias) throws KeyStoreException { return keyStore.getCertificate(alias); } public PrivateKey getPrivateKey(String alias, char[] keyPassword) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { return (PrivateKey) keyStore.getKey(alias, keyPassword); } public Certificate[] getCertificateChain(String alias) throws KeyStoreException { return keyStore.getCertificateChain(alias); } public boolean containsAlias(String alias) throws KeyStoreException { return keyStore.containsAlias(alias); } public void deleteEntry(String alias) throws KeyStoreException { keyStore.deleteEntry(alias); } public void save() throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { try (FileOutputStream fos = new FileOutputStream(keyStorePath)) { keyStore.store(fos, keyStorePassword); } } public Enumeration<String> listAliases() throws KeyStoreException { return keyStore.aliases(); } public void listContents() throws KeyStoreException { Enumeration<String> aliases = keyStore.aliases(); System.out.println("KeyStore Contents:"); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); System.out.println("Alias: " + alias); if (keyStore.isKeyEntry(alias)) { System.out.println(" Type: Private Key Entry"); } else if (keyStore.isCertificateEntry(alias)) { System.out.println(" Type: Certificate Entry"); } } } } 3. SSL Context Builder
package com.example.security.ssl; import javax.net.ssl.*; import java.io.FileInputStream; import java.security.*; import java.security.cert.CertificateException; public class SSLContextBuilder { private KeyManager[] keyManagers; private TrustManager[] trustManagers; private SecureRandom secureRandom; public SSLContextBuilder withKeyStore(String keyStorePath, char[] keyStorePassword, String keyAlias, char[] keyPassword) throws Exception { KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (FileInputStream fis = new FileInputStream(keyStorePath)) { keyStore.load(fis, keyStorePassword); } keyManagerFactory.init(keyStore, keyPassword != null ? keyPassword : keyStorePassword); this.keyManagers = keyManagerFactory.getKeyManagers(); return this; } public SSLContextBuilder withTrustStore(String trustStorePath, char[] trustStorePassword) throws Exception { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (FileInputStream fis = new FileInputStream(trustStorePath)) { trustStore.load(fis, trustStorePassword); } trustManagerFactory.init(trustStore); this.trustManagers = trustManagerFactory.getTrustManagers(); return this; } public SSLContextBuilder withCustomTrustManager(TrustManager trustManager) { this.trustManagers = new TrustManager[]{trustManager}; return this; } public SSLContextBuilder withSecureRandom(SecureRandom secureRandom) { this.secureRandom = secureRandom; return this; } public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, secureRandom); return sslContext; } // Convenience method for creating SSLContext with default settings public static SSLContext createDefaultSSLContext() throws Exception { return SSLContext.getDefault(); } } 4. Custom Trust Manager
package com.example.security.ssl; import javax.net.ssl.X509TrustManager; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class CustomTrustManager implements X509TrustManager { private final X509TrustManager defaultTrustManager; private final Set<String> allowedCertificates; public CustomTrustManager(X509TrustManager defaultTrustManager) { this.defaultTrustManager = defaultTrustManager; this.allowedCertificates = new HashSet<>(); } public void addAllowedCertificate(String certificateThumbprint) { allowedCertificates.add(certificateThumbprint.toLowerCase()); } private String getThumbprint(X509Certificate certificate) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] der = certificate.getEncoded(); md.update(der); byte[] digest = md.digest(); return bytesToHex(digest); } catch (Exception e) { throw new RuntimeException("Failed to calculate certificate thumbprint", e); } } private String bytesToHex(byte[] bytes) { StringBuilder result = new StringBuilder(); for (byte b : bytes) { result.append(String.format("%02x", b)); } return result.toString(); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // Additional client certificate validation if (chain != null && chain.length > 0) { String thumbprint = getThumbprint(chain[0]); if (!allowedCertificates.contains(thumbprint)) { throw new CertificateException("Client certificate not in allowed list"); } } defaultTrustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { // Additional server certificate validation if (chain != null && chain.length > 0) { String thumbprint = getThumbprint(chain[0]); if (!allowedCertificates.contains(thumbprint)) { throw new CertificateException("Server certificate not in allowed list"); } } defaultTrustManager.checkServerTrusted(chain, authType); } @Override public X509Certificate[] getAcceptedIssuers() { return defaultTrustManager.getAcceptedIssuers(); } public static CustomTrustManager wrapDefaultTrustManager() throws Exception { TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); for (TrustManager tm : tmf.getTrustManagers()) { if (tm instanceof X509TrustManager) { return new CustomTrustManager((X509TrustManager) tm); } } throw new IllegalStateException("No X509TrustManager found"); } } Certificate Service Implementation
1. Certificate Service
package com.example.security.service; import com.example.security.cert.CertificateUtils; import com.example.security.keystore.KeyStoreManager; import org.springframework.stereotype.Service; import java.io.File; import java.security.KeyPair; import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Date; @Service public class CertificateService { private static final String KEYSTORE_TYPE = "PKCS12"; private static final String KEY_ALGORITHM = "RSA"; private static final int KEY_SIZE = 2048; public void createSelfSignedCertificate(String keyStorePath, String keyStorePassword, String alias, String keyPassword, String subjectDN, int validityDays) throws Exception { // Create keystore directory if it doesn't exist File keyStoreFile = new File(keyStorePath); keyStoreFile.getParentFile().mkdirs(); KeyStoreManager keyStoreManager = new KeyStoreManager( keyStorePath, keyStorePassword.toCharArray(), KEYSTORE_TYPE); // Generate key pair KeyPair keyPair = CertificateUtils.generateKeyPair(KEY_ALGORITHM, KEY_SIZE); // Calculate validity dates Date startDate = new Date(); Date endDate = new Date(startDate.getTime() + validityDays * 24L * 60 * 60 * 1000); // Generate self-signed certificate X509Certificate certificate = CertificateUtils.generateSelfSignedCertificate( keyPair, subjectDN, startDate, endDate); // Add to keystore keyStoreManager.addPrivateKeyEntry( alias, keyPair.getPrivate(), keyPassword.toCharArray(), new java.security.cert.Certificate[]{certificate} ); keyStoreManager.save(); } public void importCertificate(String keyStorePath, String keyStorePassword, String alias, String certificatePath) throws Exception { KeyStoreManager keyStoreManager = new KeyStoreManager( keyStorePath, keyStorePassword.toCharArray(), KEYSTORE_TYPE); X509Certificate certificate = CertificateUtils.loadCertificateFromPEM(certificatePath); keyStoreManager.addCertificateEntry(alias, certificate); keyStoreManager.save(); } public void exportCertificate(String keyStorePath, String keyStorePassword, String alias, String exportPath) throws Exception { KeyStoreManager keyStoreManager = new KeyStoreManager( keyStorePath, keyStorePassword.toCharArray(), KEYSTORE_TYPE); java.security.cert.Certificate certificate = keyStoreManager.getCertificate(alias); if (certificate instanceof X509Certificate) { CertificateUtils.saveCertificateToPEM((X509Certificate) certificate, exportPath); } else { throw new IllegalArgumentException("Certificate is not an X509 certificate"); } } public void listCertificates(String keyStorePath, String keyStorePassword) throws Exception { KeyStoreManager keyStoreManager = new KeyStoreManager( keyStorePath, keyStorePassword.toCharArray(), KEYSTORE_TYPE); keyStoreManager.listContents(); } } 2. SSL Configuration Service
package com.example.security.service; import com.example.security.ssl.SSLContextBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.net.ssl.SSLContext; import java.io.File; @Service public class SSLConfigurationService { @Value("${app.ssl.keystore.path:keystore.p12}") private String keyStorePath; @Value("${app.ssl.keystore.password:changeit}") private String keyStorePassword; @Value("${app.ssl.truststore.path:truststore.p12}") private String trustStorePath; @Value("${app.ssl.truststore.password:changeit}") private String trustStorePassword; @Value("${app.ssl.key.alias:server}") private String keyAlias; @Value("${app.ssl.key.password:changeit}") private String keyPassword; public SSLContext createServerSSLContext() throws Exception { validateKeyStoreExists(keyStorePath); return new SSLContextBuilder() .withKeyStore(keyStorePath, keyStorePassword.toCharArray(), keyAlias, keyPassword.toCharArray()) .withTrustStore(trustStorePath, trustStorePassword.toCharArray()) .build(); } public SSLContext createClientSSLContext() throws Exception { validateKeyStoreExists(trustStorePath); return new SSLContextBuilder() .withTrustStore(trustStorePath, trustStorePassword.toCharArray()) .build(); } public SSLContext createMutualSSLContext() throws Exception { validateKeyStoreExists(keyStorePath); validateKeyStoreExists(trustStorePath); return new SSLContextBuilder() .withKeyStore(keyStorePath, keyStorePassword.toCharArray(), keyAlias, keyPassword.toCharArray()) .withTrustStore(trustStorePath, trustStorePassword.toCharArray()) .build(); } private void validateKeyStoreExists(String path) { File keyStoreFile = new File(path); if (!keyStoreFile.exists()) { throw new IllegalStateException("Keystore file not found: " + path); } } } Spring Boot Configuration
1. SSL Properties Configuration
# application.yml server: port: 8443 ssl: key-store: classpath:keystore.p12 key-store-password: changeit key-store-type: PKCS12 key-alias: server key-password: changeit trust-store: classpath:truststore.p12 trust-store-password: changeit trust-store-type: PKCS12 client-auth: need # for mutual TLS app: ssl: keystore: path: keystore.p12 password: changeit truststore: path: truststore.p12 password: changeit key: alias: server password: changeit logging: level: com.example.security: DEBUG
2. REST Client with SSL
package com.example.client; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import javax.net.ssl.SSLContext; import java.net.HttpURLConnection; @Component public class SecureRestClient { private final RestTemplate restTemplate; @Autowired public SecureRestClient(SSLContext sslContext) { this.restTemplate = createSecureRestTemplate(sslContext); } private RestTemplate createSecureRestTemplate(SSLContext sslContext) { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory() { @Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) { super.prepareConnection(connection, httpMethod); if (connection instanceof javax.net.ssl.HttpsURLConnection) { javax.net.ssl.HttpsURLConnection httpsConnection = (javax.net.ssl.HttpsURLConnection) connection; httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); // Optional: Custom hostname verifier httpsConnection.setHostnameVerifier((hostname, session) -> true); } } }; return new RestTemplate(requestFactory); } public <T> T get(String url, Class<T> responseType) { return restTemplate.getForObject(url, responseType); } public <T> ResponseEntity<T> getWithResponse(String url, Class<T> responseType) { return restTemplate.getForEntity(url, responseType); } public <T> T post(String url, Object request, Class<T> responseType) { return restTemplate.postForObject(url, request, responseType); } } 3. WebClient Configuration for SSL
package com.example.client; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; import reactor.netty.http.client.HttpClient; import javax.net.ssl.TrustManagerFactory; import java.io.InputStream; import java.security.KeyStore; @Configuration public class WebClientConfig { @Value("${app.ssl.truststore.path}") private Resource trustStoreResource; @Value("${app.ssl.truststore.password}") private String trustStorePassword; @Bean public WebClient secureWebClient() throws Exception { SslContext sslContext = createSSLContext(); HttpClient httpClient = HttpClient.create() .secure(spec -> spec.sslContext(sslContext)); return WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); } private SslContext createSSLContext() throws Exception { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (InputStream is = trustStoreResource.getInputStream()) { trustStore.load(is, trustStorePassword.toCharArray()); } TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); return SslContextBuilder.forClient() .trustManager(trustManagerFactory) .build(); } } Certificate Monitoring and Validation
1. Certificate Validator
package com.example.security.validation; import java.security.cert.X509Certificate; import java.util.Date; public class CertificateValidator { public static ValidationResult validateCertificate(X509Certificate certificate) { ValidationResult result = new ValidationResult(); try { // Check validity period Date now = new Date(); certificate.checkValidity(now); result.setValid(true); // Additional validations if (certificate.getSubjectX500Principal() == null) { result.addError("Certificate has no subject"); } if (certificate.getIssuerX500Principal() == null) { result.addError("Certificate has no issuer"); } // Check key usage boolean[] keyUsage = certificate.getKeyUsage(); if (keyUsage != null) { // Validate based on intended use } } catch (Exception e) { result.setValid(false); result.addError("Certificate validation failed: " + e.getMessage()); } return result; } public static class ValidationResult { private boolean valid; private StringBuilder errors = new StringBuilder(); public boolean isValid() { return valid; } public void setValid(boolean valid) { this.valid = valid; } public void addError(String error) { if (errors.length() > 0) { errors.append("; "); } errors.append(error); } public String getErrors() { return errors.toString(); } @Override public String toString() { return "ValidationResult{valid=" + valid + ", errors='" + errors.toString() + "'}"; } } } 2. Certificate Monitor
package com.example.security.monitoring; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Enumeration; @Component public class CertificateMonitor { private static final Logger logger = LoggerFactory.getLogger(CertificateMonitor.class); private final CertificateService certificateService; public CertificateMonitor(CertificateService certificateService) { this.certificateService = certificateService; } @Scheduled(cron = "0 0 6 * * ?") // Run daily at 6 AM public void checkCertificateExpiry() { try { // This would typically check multiple keystores checkKeystoreExpiry("keystore.p12", "changeit"); checkKeystoreExpiry("truststore.p12", "changeit"); } catch (Exception e) { logger.error("Failed to check certificate expiry", e); } } private void checkKeystoreExpiry(String keystorePath, String password) throws Exception { KeyStoreManager keyStoreManager = new KeyStoreManager( keystorePath, password.toCharArray(), "PKCS12"); Enumeration<String> aliases = keyStoreManager.listAliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); java.security.cert.Certificate cert = keyStoreManager.getCertificate(alias); if (cert instanceof X509Certificate) { checkCertificateExpiry((X509Certificate) cert, alias, keystorePath); } } } private void checkCertificateExpiry(X509Certificate cert, String alias, String keystorePath) { Date expiryDate = cert.getNotAfter(); Date now = new Date(); long daysUntilExpiry = (expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24); if (daysUntilExpiry <= 30) { logger.warn("Certificate '{}' in keystore '{}' expires in {} days", alias, keystorePath, daysUntilExpiry); } else if (daysUntilExpiry <= 7) { logger.error("Certificate '{}' in keystore '{}' expires in {} days - RENEWAL REQUIRED", alias, keystorePath, daysUntilExpiry); } } } Testing Certificate Management
1. Certificate Service Test
package com.example.security; import com.example.security.service.CertificateService; import org.junit.jupiter.api.AfterEach; 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; import java.io.File; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class CertificateServiceTest { @Autowired private CertificateService certificateService; private static final String TEST_KEYSTORE = "test-keystore.p12"; private static final String KEYSTORE_PASSWORD = "testpass"; private static final String KEY_PASSWORD = "keypass"; private static final String ALIAS = "test-cert"; @BeforeEach void setUp() { // Clean up any existing test files new File(TEST_KEYSTORE).delete(); } @AfterEach void tearDown() { // Clean up test files new File(TEST_KEYSTORE).delete(); } @Test void testCreateSelfSignedCertificate() throws Exception { // When certificateService.createSelfSignedCertificate( TEST_KEYSTORE, KEYSTORE_PASSWORD, ALIAS, KEY_PASSWORD, "CN=Test Certificate, O=Test Org", 365); // Then File keyStoreFile = new File(TEST_KEYSTORE); assertTrue(keyStoreFile.exists()); // Verify certificate can be listed assertDoesNotThrow(() -> certificateService.listCertificates(TEST_KEYSTORE, KEYSTORE_PASSWORD)); } @Test void testExportAndImportCertificate() throws Exception { // Given certificateService.createSelfSignedCertificate( TEST_KEYSTORE, KEYSTORE_PASSWORD, ALIAS, KEY_PASSWORD, "CN=Test Certificate, O=Test Org", 365); String exportPath = "test-cert.pem"; // When certificateService.exportCertificate( TEST_KEYSTORE, KEYSTORE_PASSWORD, ALIAS, exportPath); // Then File exportedFile = new File(exportPath); assertTrue(exportedFile.exists()); // Clean up exportedFile.delete(); } } 2. SSL Context Test
package com.example.security; import com.example.security.ssl.SSLContextBuilder; import org.junit.jupiter.api.Test; import javax.net.ssl.SSLContext; import static org.junit.jupiter.api.Assertions.*; class SSLContextBuilderTest { @Test void testCreateDefaultSSLContext() throws Exception { // When SSLContext sslContext = SSLContextBuilder.createDefaultSSLContext(); // Then assertNotNull(sslContext); assertEquals("TLS", sslContext.getProtocol()); } @Test void testBuildSSLContext() throws Exception { // Given - This would require actual keystore files // For testing, we'd use test resources // When & Then assertDoesNotThrow(() -> { SSLContext sslContext = new SSLContextBuilder().build(); // Basic validation assertNotNull(sslContext); }); } } Best Practices
1. Security Configuration
package com.example.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .requiresChannel(channel -> channel .anyRequest().requiresSecure() // Force HTTPS ); return http.build(); } } 2. Certificate Rotation Strategy
package com.example.security.rotation; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component public class CertificateRotationManager { @Scheduled(cron = "0 0 2 1 * ?") // Run on 1st day of month at 2 AM public void rotateCertificates() { try { LocalDateTime now = LocalDateTime.now(); logger.info("Starting certificate rotation at {}", now); // Implementation would: // 1. Generate new certificates // 2. Deploy to load balancers/servers // 3. Verify new certificates work // 4. Remove old certificates } catch (Exception e) { logger.error("Certificate rotation failed", e); } } } Conclusion
Effective certificate management in Java involves:
Key Components
- KeyStore Management - Secure storage of private keys and certificates
- TrustStore Management - Management of trusted CA certificates
- SSL Context Configuration - Proper setup of secure connections
- Certificate Validation - Regular checks for validity and security
Best Practices
- Secure Storage - Protect keystore files and passwords
- Regular Rotation - Implement certificate expiration monitoring
- Proper Validation - Custom trust managers for additional security
- Testing - Comprehensive testing of SSL configurations
- Monitoring - Continuous monitoring of certificate health
Security Considerations
- Use strong passwords for keystores and private keys
- Implement proper access controls for certificate files
- Regularly update and patch security libraries
- Monitor for certificate expiration and vulnerabilities
- Consider using hardware security modules (HSM) for production
Proper certificate management is essential for maintaining secure communications in Java applications, ensuring authentication, confidentiality, and integrity in distributed systems.