Skip to content

Commit

Permalink
Overload createKeyStore to allow password parameter to be set
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
sodrac authored and mp911de committed Jun 24, 2022
1 parent dd9c3cf commit e7fa983
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
*
* @author Mark Paluch
* @author Alex Bremora
* @author Bogdan Cardos
* @see #getPrivateKeySpec()
* @see #getX509Certificate()
* @see #getIssuingCaCertificate()
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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.
Expand All @@ -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 {

Expand All @@ -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) {
Expand All @@ -223,7 +278,7 @@ public List<X509Certificate> getX509IssuerCertificates() {

List<X509Certificate> certificates = new ArrayList<>();

for (String data : caChain) {
for (String data : this.caChain) {
try {
certificates.addAll(getCertificates(data));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
Expand All @@ -91,7 +142,7 @@ static KeyStore createKeyStore(String keyAlias, KeySpec privateKeySpec, X509Cert
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;
Expand All @@ -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 {
Expand Down Expand Up @@ -133,8 +188,12 @@ static List<X509Certificate> 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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ void before() {

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");
Expand Down Expand Up @@ -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
Expand All @@ -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<KeyFixture> keyTypeFixtures() {
Expand Down Expand Up @@ -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);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ class CertificateBundleUnitTests {

CertificateBundle certificateBundle;

@SuppressWarnings("unchecked")
@BeforeEach
void before() {
certificateBundle = loadCertificateBundle("certificate.json");
this.certificateBundle = loadCertificateBundle("certificate.json");
}

@Test
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand Down

0 comments on commit e7fa983

Please sign in to comment.