diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java b/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java index 4dfa42076..93ba650dc 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java @@ -44,6 +44,7 @@ * * @author Mark Paluch * @author Alex Bremora + * @author Bogdan Cardos * @see #getPrivateKeySpec() * @see #getX509Certificate() * @see #getIssuingCaCertificate() @@ -147,13 +148,13 @@ public String getPrivateKeyType() { */ public String getRequiredPrivateKeyType() { - String privateKeyType = getPrivateKeyType(); + String requiredPrivateKeyType = getPrivateKeyType(); - if (privateKeyType == null) { + if (requiredPrivateKeyType == null) { throw new IllegalStateException("Private key type is not set"); } - return privateKeyType; + return requiredPrivateKeyType; } /** @@ -181,6 +182,30 @@ public KeyStore createKeyStore(String keyAlias) { return createKeyStore(keyAlias, false); } + /** + * Create a {@link KeyStore} from this {@link CertificateBundle} containing the + * private key and certificate chain. + * @param keyAlias the key alias to use. + * @param password the password to use. + * @return the {@link KeyStore} containing the private key and certificate chain. + * @since 3.0.0 + */ + public KeyStore createKeyStore(String keyAlias, String password) { + return createKeyStore(keyAlias, false, password); + } + + /** + * Create a {@link KeyStore} from this {@link CertificateBundle} containing the + * private key and certificate chain. + * @param keyAlias the key alias to use. + * @param password the password to use. + * @return the {@link KeyStore} containing the private key and certificate chain. + * @since 3.0.0 + */ + public KeyStore createKeyStore(String keyAlias, char[] password) { + return createKeyStore(keyAlias, false, password); + } + /** * Create a {@link KeyStore} from this {@link CertificateBundle} containing the * private key and certificate chain. @@ -191,8 +216,38 @@ public KeyStore createKeyStore(String keyAlias) { * @since 2.3.3 */ public KeyStore createKeyStore(String keyAlias, boolean includeCaChain) { + return createKeyStore(keyAlias, includeCaChain, new char[0]); + } + + /** + * Create a {@link KeyStore} from this {@link CertificateBundle} containing the + * private key and certificate chain. + * @param keyAlias the key alias to use. + * @param includeCaChain whether to include the certificate authority chain instead of + * just the issuer certificate. + * @param password the password to use. + * @return the {@link KeyStore} containing the private key and certificate chain. + * @since 3.0.0 + */ + public KeyStore createKeyStore(String keyAlias, boolean includeCaChain, String password) { + Assert.hasText(password, "Password must not be empty"); + return createKeyStore(keyAlias, includeCaChain, password.toCharArray()); + } + + /** + * Create a {@link KeyStore} from this {@link CertificateBundle} containing the + * private key and certificate chain. + * @param keyAlias the key alias to use. + * @param includeCaChain whether to include the certificate authority chain instead of + * just the issuer certificate. + * @param password the password to use. + * @return the {@link KeyStore} containing the private key and certificate chain. + * @since 3.0.0 + */ + public KeyStore createKeyStore(String keyAlias, boolean includeCaChain, char[] password) { Assert.hasText(keyAlias, "Key alias must not be empty"); + Assert.notNull(password, "Password must not be null"); try { @@ -206,7 +261,7 @@ public KeyStore createKeyStore(String keyAlias, boolean includeCaChain) { certificates.add(getX509IssuerCertificate()); } - return KeystoreUtil.createKeyStore(keyAlias, getPrivateKeySpec(), + return KeystoreUtil.createKeyStore(keyAlias, getPrivateKeySpec(), password, certificates.toArray(new X509Certificate[0])); } catch (GeneralSecurityException | IOException e) { @@ -223,7 +278,7 @@ public List getX509IssuerCertificates() { List certificates = new ArrayList<>(); - for (String data : caChain) { + for (String data : this.caChain) { try { certificates.addAll(getCertificates(data)); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java b/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java index 99ba916a3..b5a3456c3 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/KeystoreUtil.java @@ -32,11 +32,14 @@ import java.util.Collections; import java.util.List; +import org.springframework.util.Assert; + /** * Keystore utility to create a {@link KeyStore} containing a {@link CertificateBundle} * with the certificate chain and its private key. * * @author Mark Paluch + * @author Bogdan Cardos */ class KeystoreUtil { @@ -73,14 +76,62 @@ class KeystoreUtil { /** * Create a {@link KeyStore} containing the {@link KeySpec} and {@link X509Certificate * certificates} using the given {@code keyAlias}. - * @param keyAlias - * @param certificates - * @return - * @throws GeneralSecurityException - * @throws IOException + * @param keyAlias the key alias to use. + * @param privateKeySpec the private key to use. + * @param certificates the certificate chain to use. + * @return the {@link KeyStore} containing the private key and certificate chain. + * @throws GeneralSecurityException if exception occur when creating the instance of + * the {@link KeyStore} + * @throws IOException if there is an I/O or format problem with the keystore data, if + * a password is required but not given, or if the given password was incorrect. If + * the error is due to a wrong password, the {@link Throwable#getCause cause} of the + * {@code IOException} should be an {@code UnrecoverableKeyException} */ static KeyStore createKeyStore(String keyAlias, KeySpec privateKeySpec, X509Certificate... certificates) throws GeneralSecurityException, IOException { + return createKeyStore(keyAlias, privateKeySpec, new char[0], certificates); + } + + /** + * Create a {@link KeyStore} containing the {@link KeySpec} and {@link X509Certificate + * certificates} using the given {@code keyAlias} and {@code keyPassword}. + * @param keyAlias the key alias to use. + * @param privateKeySpec the private key to use. + * @param keyPassword the password to use. + * @param certificates the certificate chain to use. + * @return the {@link KeyStore} containing the private key and certificate chain. + * @throws GeneralSecurityException if exception occur when creating the instance of + * the {@link KeyStore} + * @throws IOException if there is an I/O or format problem with the keystore data, if + * a password is required but not given, or if the given password was incorrect. If + * the error is due to a wrong password, the {@link Throwable#getCause cause} of the + * {@code IOException} should be an {@code UnrecoverableKeyException} + */ + static KeyStore createKeyStore(String keyAlias, KeySpec privateKeySpec, String keyPassword, + X509Certificate... certificates) throws GeneralSecurityException, IOException { + Assert.hasText(keyPassword, "keyPassword must not be empty"); + return createKeyStore(keyAlias, privateKeySpec, keyPassword.toCharArray(), certificates); + } + + /** + * Create a {@link KeyStore} containing the {@link KeySpec} and {@link X509Certificate + * certificates} using the given {@code keyAlias} and {@code keyPassword}. + * @param keyAlias the key alias to use. + * @param privateKeySpec the private key to use. + * @param keyPassword the password to use. + * @param certificates the certificate chain to use. + * @return the {@link KeyStore} containing the private key and certificate chain. + * @throws GeneralSecurityException if exception occur when creating the instance of + * the {@link KeyStore} + * @throws IOException if there is an I/O or format problem with the keystore data, if + * a password is required but not given, or if the given password was incorrect. If + * the error is due to a wrong password, the {@link Throwable#getCause cause} of the + * {@code IOException} should be an {@code UnrecoverableKeyException} + */ + static KeyStore createKeyStore(String keyAlias, KeySpec privateKeySpec, char[] keyPassword, + X509Certificate... certificates) throws GeneralSecurityException, IOException { + + Assert.notNull(keyPassword, "keyPassword must not be null"); PrivateKey privateKey = (privateKeySpec instanceof RSAPrivateKeySpec || privateKeySpec instanceof PKCS8EncodedKeySpec) ? RSA_KEY_FACTORY.generatePrivate(privateKeySpec) @@ -91,7 +142,7 @@ static KeyStore createKeyStore(String keyAlias, KeySpec privateKeySpec, X509Cert List certChain = new ArrayList<>(); Collections.addAll(certChain, certificates); - keyStore.setKeyEntry(keyAlias, privateKey, new char[0], + keyStore.setKeyEntry(keyAlias, privateKey, keyPassword, certChain.toArray(new java.security.cert.Certificate[certChain.size()])); return keyStore; @@ -100,10 +151,14 @@ static KeyStore createKeyStore(String keyAlias, KeySpec privateKeySpec, X509Cert /** * Create a {@link KeyStore} containing the {@link X509Certificate certificates} * stored with as {@code cert_0, cert_1...cert_N}. - * @param certificates - * @return - * @throws GeneralSecurityException - * @throws IOException + * @param certificates the certificate chain to use. + * @return the {@link KeyStore} containing the certificate chain. + * @throws GeneralSecurityException if exception occur when creating the instance of + * the {@link KeyStore} + * @throws IOException if there is an I/O or format problem with the keystore data, if + * a password is required but not given, or if the given password was incorrect. If + * the error is due to a wrong password, the {@link Throwable#getCause cause} of the + * {@code IOException} should be an {@code UnrecoverableKeyException} * @since 2.0 */ static KeyStore createKeyStore(X509Certificate... certificates) throws GeneralSecurityException, IOException { @@ -133,8 +188,12 @@ static List getCertificates(byte[] source) throws CertificateEx /** * Create an empty {@link KeyStore}. * @return the {@link KeyStore}. - * @throws GeneralSecurityException - * @throws IOException + * @throws GeneralSecurityException if exception occur when creating the instance of + * the {@link KeyStore} + * @throws IOException if there is an I/O or format problem with the keystore data, if + * a password is required but not given, or if the given password was incorrect. If + * the error is due to a wrong password, the {@link Throwable#getCause cause} of the + * {@code IOException} should be an {@code UnrecoverableKeyException} */ private static KeyStore createKeyStore() throws GeneralSecurityException, IOException { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java b/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java index 3fe00f9d0..50346b636 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java @@ -276,14 +276,12 @@ byte[] getContent() { enum PemObjectType { - CERTIFICATE_REQUEST("CERTIFICATE REQUEST"), NEW_CERTIFICATE_REQUEST("NEW CERTIFICATE REQUEST"), CERTIFICATE( - "CERTIFICATE"), TRUSTED_CERTIFICATE("TRUSTED CERTIFICATE"), X509_CERTIFICATE( - "X509 CERTIFICATE"), X509_CRL("X509 CRL"), PKCS7("PKCS7"), CMS("CMS"), ATTRIBUTE_CERTIFICATE( - "ATTRIBUTE CERTIFICATE"), EC_PARAMETERS( - "EC PARAMETERS"), PUBLIC_KEY("PUBLIC KEY"), RSA_PUBLIC_KEY( - "RSA PUBLIC KEY"), RSA_PRIVATE_KEY("RSA PRIVATE KEY"), EC_PRIVATE_KEY( - "EC PRIVATE KEY"), ENCRYPTED_PRIVATE_KEY( - "ENCRYPTED PRIVATE KEY"), PRIVATE_KEY("PRIVATE KEY"); + CERTIFICATE_REQUEST("CERTIFICATE REQUEST"), NEW_CERTIFICATE_REQUEST("NEW CERTIFICATE REQUEST"), + CERTIFICATE("CERTIFICATE"), TRUSTED_CERTIFICATE("TRUSTED CERTIFICATE"), X509_CERTIFICATE("X509 CERTIFICATE"), + X509_CRL("X509 CRL"), PKCS7("PKCS7"), CMS("CMS"), ATTRIBUTE_CERTIFICATE("ATTRIBUTE CERTIFICATE"), + EC_PARAMETERS("EC PARAMETERS"), PUBLIC_KEY("PUBLIC KEY"), RSA_PUBLIC_KEY("RSA PUBLIC KEY"), + RSA_PRIVATE_KEY("RSA PRIVATE KEY"), EC_PRIVATE_KEY("EC PRIVATE KEY"), + ENCRYPTED_PRIVATE_KEY("ENCRYPTED PRIVATE KEY"), PRIVATE_KEY("PRIVATE KEY"); // cache private static final PemObjectType[] constants = values(); diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java index 1c31591aa..5d7305b09 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultPkiTemplateIntegrationTests.java @@ -109,7 +109,7 @@ void before() { this.vaultOperations.write("pki/config/ca", pembundle); - Map role = new HashMap(); + Map role = new HashMap<>(); role.put("allowed_domains", "localhost,example.com"); role.put("allow_subdomains", "true"); role.put("allow_localhost", "true"); @@ -150,8 +150,20 @@ void issueCertificateShouldCreateCertificate() throws KeyStoreException { KeyStore keyStore = data.createKeyStore("vault"); assertThat(keyStore.getCertificateChain("vault")).hasSize(2); + KeyStore keyStoreWithPassword = data.createKeyStore("vault", "mypassword"); + assertThat(keyStoreWithPassword.getCertificateChain("vault")).hasSize(2); + + KeyStore keyStoreWithPasswordChar = data.createKeyStore("vault", new char[0]); + assertThat(keyStoreWithPasswordChar.getCertificateChain("vault")).hasSize(2); + KeyStore keyStoreWithCaChain = data.createKeyStore("vault", true); assertThat(keyStoreWithCaChain.getCertificateChain("vault")).hasSize(3); + + KeyStore keyStoreWithCaChainAndPassword = data.createKeyStore("vault", true, "mypassword"); + assertThat(keyStoreWithCaChainAndPassword.getCertificateChain("vault")).hasSize(3); + + KeyStore keyStoreWithCaChainAndPasswordChar = data.createKeyStore("vault", true, new char[0]); + assertThat(keyStoreWithCaChainAndPasswordChar.getCertificateChain("vault")).hasSize(3); } @ParameterizedTest @@ -175,8 +187,21 @@ void issueCertificateUsingFormat(KeyFixture keyFixture) throws Exception { KeyStore keyStore = data.createKeyStore("vault"); assertThat(keyStore.getCertificateChain("vault")).hasSize(2); + KeyStore keyStoreWithPassword = data.createKeyStore("vault", "mypassword"); + assertThat(keyStoreWithPassword.getCertificateChain("vault")).hasSize(2); + + KeyStore keyStoreWithPasswordChar = data.createKeyStore("vault", new char[0]); + assertThat(keyStoreWithPasswordChar.getCertificateChain("vault")).hasSize(2); + KeyStore keyStoreWithCaChain = data.createKeyStore("vault", true); assertThat(keyStoreWithCaChain.getCertificateChain("vault")).hasSize(3); + + KeyStore keyStoreWithCaChainAndPassword = data.createKeyStore("vault", true, "mypassword"); + assertThat(keyStoreWithCaChainAndPassword.getCertificateChain("vault")).hasSize(3); + + KeyStore keyStoreWithCaChainAndPasswordChar = data.createKeyStore("vault", true, new char[0]); + assertThat(keyStoreWithCaChainAndPasswordChar.getCertificateChain("vault")).hasSize(3); + } static Stream keyTypeFixtures() { @@ -213,7 +238,7 @@ static class KeyFixture { @Override public String toString() { - return String.format("[%s, %s, %s]", format, privateKeyFormat, keyType); + return String.format("[%s, %s, %s]", this.format, this.privateKeyFormat, this.keyType); } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java index 697cda5d0..186df11ff 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/support/CertificateBundleUnitTests.java @@ -41,10 +41,9 @@ class CertificateBundleUnitTests { CertificateBundle certificateBundle; - @SuppressWarnings("unchecked") @BeforeEach void before() { - certificateBundle = loadCertificateBundle("certificate.json"); + this.certificateBundle = loadCertificateBundle("certificate.json"); } @Test @@ -103,9 +102,16 @@ void getAsKeystore() throws Exception { CertificateBundle bundle = loadCertificateBundle("certificate.json"); KeyStore keyStore = bundle.createKeyStore("mykey"); - assertThat(keyStore.size()).isEqualTo(1); assertThat(keyStore.getCertificateChain("mykey")).hasSize(2); + + KeyStore keyStoreWithPassword = bundle.createKeyStore("mykey", "mypassword"); + assertThat(keyStoreWithPassword.size()).isEqualTo(1); + assertThat(keyStoreWithPassword.getCertificateChain("mykey")).hasSize(2); + + KeyStore keyStoreWithPasswordChar = bundle.createKeyStore("mykey", new char[0]); + assertThat(keyStoreWithPasswordChar.size()).isEqualTo(1); + assertThat(keyStoreWithPasswordChar.getCertificateChain("mykey")).hasSize(2); } @ParameterizedTest @@ -116,8 +122,13 @@ void createKeystore(String path) { CertificateBundle bundle = loadCertificateBundle(path); KeyStore keyStore = bundle.createKeyStore("localhost"); - assertThat(keyStore).isNotNull(); + + KeyStore keyStoreWithPassword = bundle.createKeyStore("localhost", "mypassword"); + assertThat(keyStoreWithPassword).isNotNull(); + + KeyStore keyStoreWithPasswordChar = bundle.createKeyStore("localhost", new char[0]); + assertThat(keyStoreWithPasswordChar).isNotNull(); } @ParameterizedTest @@ -126,11 +137,15 @@ void shouldCreateKeystore(String path) { CertificateBundle bundle = loadCertificateBundle(path); KeyStore keyStore = bundle.createKeyStore("localhost"); - assertThat(keyStore).isNotNull(); + + KeyStore keyStoreWithPassword = bundle.createKeyStore("localhost", "mypassword"); + assertThat(keyStoreWithPassword).isNotNull(); + + KeyStore keyStoreWithPasswordChar = bundle.createKeyStore("localhost", new char[0]); + assertThat(keyStoreWithPasswordChar).isNotNull(); } - @SuppressWarnings("unchecked") CertificateBundle loadCertificateBundle(String path) { try {