Overload createKeyStore to allow password parameter to be set

- Overload the CertificateBundle.createKeyStore() to allow a String password parameter to be set
- Overload the static KeystoreUtil.createKeyStore() to allow a String password parameter to be set
- Added javadoc and unit tests
- Code formatting, best practices and cosmetic changes

Fixes gh-708
Original pull request: gh-711
This commit is contained in:
Bogdan Cardoş
2022-06-15 04:25:05 +03:00
committed by Mark Paluch
parent dd9c3cfab8
commit e7fa983fa4
5 changed files with 185 additions and 33 deletions

View File

@@ -44,6 +44,7 @@ import org.springframework.vault.VaultException;
*
* @author Mark Paluch
* @author Alex Bremora
* @author Bogdan Cardos
* @see #getPrivateKeySpec()
* @see #getX509Certificate()
* @see #getIssuingCaCertificate()
@@ -147,13 +148,13 @@ public class CertificateBundle extends Certificate {
*/
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 class CertificateBundle extends Certificate {
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 class CertificateBundle extends Certificate {
* @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 class CertificateBundle extends Certificate {
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 class CertificateBundle extends Certificate {
List<X509Certificate> certificates = new ArrayList<>();
for (String data : caChain) {
for (String data : this.caChain) {
try {
certificates.addAll(getCertificates(data));
}

View File

@@ -32,11 +32,14 @@ import java.util.ArrayList;
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 @@ class KeystoreUtil {
List<X509Certificate> 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 @@ class KeystoreUtil {
/**
* 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 @@ class KeystoreUtil {
/**
* 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 {

View File

@@ -276,14 +276,12 @@ public class PemObject {
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();

View File

@@ -109,7 +109,7 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
this.vaultOperations.write("pki/config/ca", pembundle);
Map<String, String> role = new HashMap<String, String>();
Map<String, String> role = new HashMap<>();
role.put("allowed_domains", "localhost,example.com");
role.put("allow_subdomains", "true");
role.put("allow_localhost", "true");
@@ -150,8 +150,20 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
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 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
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<KeyFixture> keyTypeFixtures() {
@@ -213,7 +238,7 @@ class VaultPkiTemplateIntegrationTests extends IntegrationTestSupport {
@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);
}
}

View File

@@ -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 @@ class CertificateBundleUnitTests {
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 @@ class CertificateBundleUnitTests {
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 @@ class CertificateBundleUnitTests {
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 {