[ldap-sdk-commits] SF.net SVN: ldap-sdk:[1580] trunk
A Java-based LDAP API
Brought to you by:
dirmgr,
kennethleo
|
From: <di...@us...> - 2022-12-09 21:22:00
|
Revision: 1580
http://sourceforge.net/p/ldap-sdk/code/1580
Author: dirmgr
Date: 2022-12-09 21:21:56 +0000 (Fri, 09 Dec 2022)
Log Message:
-----------
Add support for encrypted PKCS #8 private keys
Added support for encrypted PKCS #8 private keys. Private keys can
now be formatted in encrypted PEM when provided with an encryption
password and a set of encryption properties, and the PKCS #8 PEM
file reader can read encrypted private keys when provided with the
encryption password. The manage-certificates export-private-key
command has been updated to support writing an encrypted
representation of the private key in either PEM or DER form, and
manage-certificates import-certificate has been updated to support
obtaining the private key when it is encrypted form.
Modified Paths:
--------------
trunk/docs/release-notes.html
trunk/messages/unboundid-ldapsdk-cert.properties
trunk/src/com/unboundid/ldap/sdk/unboundidds/extensions/CertificateDataReplaceCertificateKeyStoreContent.java
trunk/src/com/unboundid/util/ssl/PEMFileKeyManager.java
trunk/src/com/unboundid/util/ssl/cert/ManageCertificates.java
trunk/src/com/unboundid/util/ssl/cert/PKCS8PEMFileReader.java
trunk/src/com/unboundid/util/ssl/cert/PKCS8PrivateKey.java
trunk/tests/unit/src/com/unboundid/ldap/sdk/unboundidds/extensions/CertificateDataReplaceCertificateKeyStoreContentTestCase.java
trunk/tests/unit/src/com/unboundid/util/ssl/cert/ManageCertificatesTestCase.java
Added Paths:
-----------
trunk/src/com/unboundid/util/ssl/cert/PKCS5AlgorithmIdentifier.java
trunk/src/com/unboundid/util/ssl/cert/PKCS8EncryptionHandler.java
trunk/src/com/unboundid/util/ssl/cert/PKCS8EncryptionProperties.java
trunk/tests/unit/src/com/unboundid/util/ssl/cert/PKCS5AlgorithmIdentifierTestCase.java
trunk/tests/unit/src/com/unboundid/util/ssl/cert/PKCS8EncryptionHandlerTestCase.java
trunk/tests/unit/src/com/unboundid/util/ssl/cert/PKCS8EncryptionPropertiesTestCase.java
Modified: trunk/docs/release-notes.html
===================================================================
--- trunk/docs/release-notes.html 2022-12-08 17:49:08 UTC (rev 1579)
+++ trunk/docs/release-notes.html 2022-12-09 21:21:56 UTC (rev 1580)
@@ -6,6 +6,30 @@
<h2>Release Notes</h2>
+ <h3>Version 6.0.8</h3>
+
+ <p>
+ The following changes were made between the 6.0.7 and 6.0.8 releases:
+ </p>
+
+ <ul>
+ <li>
+ Added support for encrypted PKCS #8 private keys. Private keys can now be
+ formatted in encrypted PEM when provided with an encryption password and a set
+ of encryption properties, and the PKCS #8 PEM file reader can read encrypted
+ private keys when provided with the encryption password. The
+ manage-certificates export-private-key command has been updated to support
+ writing an encrypted representation of the private key in either PEM or DER
+ form, and manage-certificates import-certificate has been updated to support
+ obtaining the private key when it is encrypted form.
+ <br><br>
+ </li>
+ </ul>
+
+ <p></p>
+
+
+
<h3>Version 6.0.7</h3>
<p>
Modified: trunk/messages/unboundid-ldapsdk-cert.properties
===================================================================
--- trunk/messages/unboundid-ldapsdk-cert.properties 2022-12-08 17:49:08 UTC (rev 1579)
+++ trunk/messages/unboundid-ldapsdk-cert.properties 2022-12-09 21:21:56 UTC (rev 1580)
@@ -545,6 +545,23 @@
optional when using the PEM format, but required when using the DER \
format. If no output file is provided, then the exported private key will \
be written to standard output.
+INFO_MANAGE_CERTS_SC_EXPIRT_KEY_ARG_ENC_PW_DESC=The password to use to encrypt \
+ the exported private key. At most one of the --encryption-password, \
+ --encryption-password-file, and --prompt-for-encryption-password arguments \
+ may be provided. If none of those arguments is provided, then the private \
+ key will not be encrypted.
+INFO_MANAGE_CERTS_SC_EXPORT_KEY_ARG_ENC_PW_FILE_DESC=The path to a file \
+ containing the password to use to encrypt the exported private key. If \
+ provided, the file must exist, must contain only one line, and that line \
+ must consist only of the clear-text encryption password. At most one of the \
+ --encryption-password, --encryption-password-file, and \
+ --prompt-for-encryption-password arguments may be provided. If none of \
+ those arguments is provided, then the private key will not be encrypted.
+INFO_MANAGE_CERTS_SC_EXPORT_KEY_ARG_PROMPT_FOR_ENC_PW=Interactively prompt for \
+ the password to use to encrypt the exported private key. At most one of the \
+ --encryption-password, --encryption-password-file, and \
+ --prompt-for-encryption-password arguments may be provided. If none of \
+ those arguments is provided, then the private key will not be encrypted.
INFO_MANAGE_CERTS_SC_EXPORT_KEY_EXAMPLE_1=Export the private key for the \
''server-cert'' certificate to standard output in PEM format.
INFO_MANAGE_CERTS_SC_EXPORT_KEY_EXAMPLE_2=Export the private key for the \
@@ -670,6 +687,25 @@
password that does not match the key store password, then one of the \
--private-key-password, --private-key-password-file, or \
--prompt-for-private-key-password arguments should be provided.
+INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_ENC_PW_DESC=The password used to encrypt \
+ the private key being imported. At most one of the --encryption-password, \
+ --encryption-password-file, and --prompt-for-encryption-password arguments \
+ may be provided. If none of those arguments is provided, then it will be \
+ assumed that the private key is not encrypted.
+INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_ENC_PW_FILE_DESC=The path to a file \
+ containing the password used to encrypt the private key being imported. If \
+ provided, the file must exist, must contain only one line, and that line \
+ must consist only of the clear-text encryption password. At most one of the \
+ --encryption-password, --encryption-password-file, and \
+ --prompt-for-encryption-password arguments may be provided. If none of \
+ those arguments is provided, then it will be assumed that the private key is \
+ not encrypted.
+INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_PROMPT_FOR_ENC_PW_DESC=Interactively \
+ prompt for the password used to encrypt the private key being imported. At \
+ most one of the --encryption-password, --encryption-password-file, and \
+ --prompt-for-encryption-password arguments may be provided. If none of \
+ those arguments is provided, then it will be assumed that the private key is \
+ not encrypted.
INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_NO_PROMPT_DESC=Import the certificates \
without prompting the end user. By default, the certificates will be \
displayed and the user will be interactively prompted about whether to \
@@ -2624,6 +2660,17 @@
INFO_MANAGE_CERTS_KEY_KS_PW_NEW_PROMPT_2=Confirm the key store password:
ERR_MANAGE_CERTS_KEY_KS_PW_PROMPT_MISMATCH=ERROR: The provided passwords do \
not match.
+ERR_MANAGE_CERTS_GET_PK_ENC_PW_ERROR_READING_FILE=ERROR: An error occurred \
+ while attempting to read the private key encryption password from file \
+ ''{0}'': {1}
+INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_DECRYPT=Enter the password used to \
+ encrypt the private key:
+INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_ENCRYPT_1=Enter the password to use to \
+ encrypt the private key:
+INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_ENCRYPT_2=Confirm the private key \
+ encryption password:
+INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_MISMATCH=ERROR: The private key \
+ encryption passwords do not match.
ERR_MANAGE_CERTS_PROMPT_FOR_PW_EMPTY_PW=ERROR: The password must not be empty.
ERR_MANAGE_CERTS_PROMPT_FOR_YES_NO_INVALID_RESPONSE=ERROR: Your response \
must be either ''yes'' or ''no''.
@@ -2718,6 +2765,11 @@
PEM-encoded private key from file ''{0}'' because the file contains \
multiple ''BEGIN PRIVATE KEY'' headers. A private key file may only \
contain a single private key.
+ERR_MANAGE_CERTS_READ_PK_FROM_FILE_PK_ENCRYPTED_NO_PW=ERROR: File ''{0}'' \
+ appears to contain the PEM representation of an encrypted private key, but \
+ no encryption password was provided. Use one of the --encryption-password, \
+ --encryption-password-file, or --prompt-for-encryption-password arguments to \
+ specify the encryption password.
ERR_MANAGE_CERTS_READ_PK_FROM_FILE_END_WITHOUT_BEGIN=ERROR: Unable to read \
a PEM-encoded private key from file ''{0}'' because the file contains an \
''END PRIVATE KEY'' footer without a corresponding ''BEGIN PRIVATE KEY'' \
@@ -2917,6 +2969,9 @@
ERR_PKCS8_PEM_READER_REPEATED_BEGIN=Unable to read a PEM-encoded PKCS #8 \
private key because an unexpected ''{0}'' header was found after having \
already begun the process of decoding a private key.
+ERR_PKCS8_PEM_READER_NO_PW_FOR_ENCRYPTED_KEY=Unable to read a PEM-encoded \
+ PKCS #8 private key because the private key is encrypted but no encryption \
+ password was provided.
ERR_PKCS8_PEM_READER_END_WITHOUT_BEGIN=Unable to read a PEM-encoded PKCS #8 \
private key because an unexpected ''{0}'' footer was found without a \
preceding ''{1}'' header.
@@ -2928,3 +2983,99 @@
ERR_PKCS8_PEM_READER_DATA_WITHOUT_BEGIN=Unable to read a PEM-encoded PKCS #8 \
private key because unexpected data was found before encountering a \
''{0}'' header.
+INFO_PKCS5_ALG_ID_DESC_PBES2=The PBES2 encryption scheme
+INFO_PKCS5_ALG_ID_DESC_PBKDF2=The PBKDF2 key derivation function
+INFO_PKCS5_ALG_ID_DESC_HMAC_SHA_1=The HMAC-SHA-1 pseudorandom function
+INFO_PKCS5_ALG_ID_DESC_HMAC_SHA_224=The HMAC-SHA-224 pseudorandom function
+INFO_PKCS5_ALG_ID_DESC_HMAC_SHA_256=The HMAC-SHA-256 pseudorandom function
+INFO_PKCS5_ALG_ID_DESC_HMAC_SHA_384=The HMAC-SHA-384 pseudorandom function
+INFO_PKCS5_ALG_ID_DESC_HMAC_SHA_512=The HMAC-SHA-512 pseudorandom function
+INFO_PKCS5_ALG_ID_DESC_DES_EDE_CBC_PAD=The DESede/CBC/PKCS5Padding cipher \
+ transformation
+INFO_PKCS5_ALG_ID_DESC_AES_128_CBC_PAD=The 128-bit AES/CBC/PKCS5Padding \
+ cipher transformation
+INFO_PKCS5_ALG_ID_DESC_AES_192_CBC_PAD=The 192-bit AES/CBC/PKCS5Padding \
+ cipher transformation
+INFO_PKCS5_ALG_ID_DESC_AES_256_CBC_PAD=The 256-bit AES/CBC/PKCS5Padding \
+ cipher transformation
+ERR_PKCS8_ENC_HANDLER_CANNOT_PARSE_AS_ENC_KEY_SEQUENCE=Unable to decode an \
+ encrypted PKCS #8 private key because the encrypted key bytes cannot be \
+ parsed as a valid DER sequence.
+ERR_PKCS8_ENC_HANDLER_SEQUENCE_UNEXPECTED_ENC_KEY_ELEMENT_COUNT=Unable to \
+ decode an encrypted PKCS #8 private key because the encrypted key sequence \
+ did not contain the expected 2 elements (intended to represent the \
+ encryption scheme algorithm identifier and the encrypted key data). The \
+ number of elements in the sequence was {0,number,0}.
+ERR_PKCS8_ENC_HANDLER_KEY_SCHEME_ELEMENT_NOT_SEQUENCE=Unable to decode an \
+ encrypted PKCS #8 private key because the first element of the encrypted \
+ key sequence could not be parsed as a sequence (intended to hold the \
+ encryption scheme algorithm identifier).
+ERR_PKCS8_ENC_HANDLER_SEQUENCE_UNEXPECTED_KEY_SCHEME_ELEMENT_COUNT=Unable to \
+ decode an encrypted PKCS #8 private key because the encryption scheme \
+ sequence did not contain the expected 2 elements (intended to represent \
+ the encryption scheme OID and parameters). The number of elements in the \
+ sequence was {0,number,0}.
+ERR_PKCS8_ENC_HANDLER_CANNOT_PARSE_KEY_SCHEME_OID=Unable to decode an \
+ encrypted PKCS #8 private key because the first element of the encryption \
+ scheme sequence could not be parsed as an OID intended to identify the \
+ encryption scheme algorithm.
+ERR_PKCS8_ENC_HANDLER_ENC_SCHEME_NOT_PBES2=Unable to decode an encrypted \
+ PKCS #8 private key because it used an unsupported encryption scheme \
+ algorithm with OID ''{0}''. Only the PBES2 encryption scheme is supported.
+ERR_PKCS8_ENC_HANDLER_PBES2_PARAMS_NOT_SEQUENCE=Unable to decode an encrypted \
+ PKCS #8 private key because the PBES2 algorithm parameters could not be \
+ parsed as a sequence (intended to hold the algorithm identifiers for the \
+ key derivation function and encryption algorithm).
+ERR_PKCS8_ENC_HANDLER_PBES2_UNEXPECTED_PARAMS_SEQUENCE_ELEMENT_COUNT=Unable \
+ to decode an encrypted PKCS #8 private key because the PBES2 algorithm \
+ parameters sequence did not contain the expected 2 elements (intended to \
+ represent the algorithm identifiers for the key derivation function and \
+ encryption algorithm). The number of elements in the sequence was \
+ {0,number,0}.
+ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_KDF=Unable to decode an encrypted PKCS #8 \
+ private key because the PBES2 algorithm parameters sequence indicated that \
+ it uses an unsupported key derivation function with OID ''{0}''. Only the \
+ PKBDF2 key derivation function is supported.
+ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_PBKDF2_PRF=Unable to decode an encrypted \
+ PKCS #8 private key because the PBKDF2 key derivation function uses an \
+ unsupported pseudorandom function with oid ''{0}''.
+ERR_PKCS8_ENC_HANDLER_CANNOT_DECODE_KDF_SETTINGS=Unable to decode an \
+ encrypted PKCS #8 private key because an unexpected error occurred while \
+ attempting to decode the key derivation function settings: {0}
+ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_CIPHER=Unable to decode an encrypted PKCS \
+ #8 private key because the PBES2 algorithm parameters sequence indicated \
+ that it uses an unsupported cipher transformation with OID ''{0}''.
+ERR_PKCS8_ENC_HANDLER_CANNOT_DECODE_CIPHER_SETTINGS=Unable to decode an \
+ encrypted PKCS #8 private key because an unexpected error occurred while \
+ attempting to decode the encryption cipher settings: {0}
+ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_DEC_SECRET_KEY=Unable to decode an \
+ encrypted PKCS #8 private key because an error occurred while attempting to \
+ create the secret key using the ''{0}'' algorithm with the provided \
+ password: {1}
+ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_DEC_CIPHER=Unable to decode an encrypted \
+ PKCS #8 private key because an error occurred while attempting to create a \
+ cipher using the ''{0}'' cipher transformation: {1}
+ERR_PKCS8_ENC_HANDLER_CANNOT_DECRYPT_KEY=Unable to decode an encrypted \
+ PKCS #8 private key because an error occurred while attempting to decrypt \
+ the encrypted key data using the ''{0}'' cipher transformation: {1}
+ERR_PKCS8_ENC_HANDLER_CANNOT_PARSE_DECRYPTED_KEY=Unable to decode an \
+ encrypted PKCS #8 private key because although the encrypted key data was \
+ successfully decrypted using the ''{0}'' cipher transformation, the \
+ decrypted key could not be parsed as a valid PKCS #8 secret key: {1}
+ERR_PKCS8_ENC_PROPS_INVALID_KEY_FACTORY_PRF_ALG=Unable to use {0} ({1}) as a \
+ key factory pseudorandom function because that identifier does not \
+ represent a supported pseudorandom function.
+ERR_PKCS8_ENC_PROPS_INVALID_CIPHER_TRANSFORMATION_ALG=Unable to use {0} ({1}) \
+ as a cipher transformation because that identifier does not represent a \
+ supported cipher transformation.
+ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_ENC_SECRET_KEY=Unable to encrypt the \
+ provided PKCS #8 private key because an error occurred while attempting to \
+ create the secret key using the ''{0}'' algorithm with the provided \
+ password: {1}
+ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_ENC_CIPHER=Unable to encrypt the provided \
+ PKCS #8 private key because an error occurred while attempting to create a \
+ cipher using the ''{0}'' cipher transformation: {1}
+ERR_PKCS8_ENC_HANDLER_CANNOT_ENCRYPT_PRIVATE_KEY=Unable to encrypt the \
+ provided PKCS #8 private key using the ''{0}'' cipher transformation: {1}
+ERR_PKCS8_ENC_HANDLER_CANNOT_ENCODE_ENC_PRIVATE_KEY=Unable to encode the \
+ encrypted PKCS #8 private key: {0}
Modified: trunk/src/com/unboundid/ldap/sdk/unboundidds/extensions/CertificateDataReplaceCertificateKeyStoreContent.java
===================================================================
--- trunk/src/com/unboundid/ldap/sdk/unboundidds/extensions/CertificateDataReplaceCertificateKeyStoreContent.java 2022-12-08 17:49:08 UTC (rev 1579)
+++ trunk/src/com/unboundid/ldap/sdk/unboundidds/extensions/CertificateDataReplaceCertificateKeyStoreContent.java 2022-12-09 21:21:56 UTC (rev 1580)
@@ -189,7 +189,8 @@
* This may be {@code null} if the new
* end-entity certificate uses the same private
* key as the certificate currently in use in
- * the server.
+ * the server. The private key must not be
+ * encrypted.
*
* @throws LDAPException If a problem occurs while trying to read or parse
* data contained in any of the provided files.
@@ -206,6 +207,48 @@
/**
+ * Creates a new instance of this key store content object with the provided
+ * information.
+ *
+ * @param certificateChainFiles A list containing one or more files from
+ * which to read the PEM or DER representations
+ * of the X.509 certificates to include in
+ * the new certificate chain. The order of
+ * the files, and the order of the certificates
+ * in each file, should be arranged such that
+ * the first certificate read is the end-entity
+ * certificate and each subsequent certificate
+ * is the issuer for the previous. This must
+ * not be {@code null} or empty.
+ * @param privateKeyFile A file from which to read the PEM or DER
+ * representation of the PKCS #8 private key
+ * for the end-entity certificate in the chain.
+ * This may be {@code null} if the new
+ * end-entity certificate uses the same private
+ * key as the certificate currently in use in
+ * the server.
+ * @param privateKeyEncryptionPassword
+ * The password needed to decrypt the private key if it is
+ * encrypted. This may be {@code null} if the private key is not
+ * encrypted.
+ *
+ * @throws LDAPException If a problem occurs while trying to read or parse
+ * data contained in any of the provided files.
+ */
+ public CertificateDataReplaceCertificateKeyStoreContent(
+ @NotNull final List<File> certificateChainFiles,
+ @Nullable final File privateKeyFile,
+ @Nullable final char[] privateKeyEncryptionPassword)
+ throws LDAPException
+ {
+ this(readCertificateChain(certificateChainFiles),
+ ((privateKeyFile == null) ? null :
+ readPrivateKey(privateKeyFile, privateKeyEncryptionPassword)));
+ }
+
+
+
+ /**
* Reads a certificate chain from the given file or set of files. Each file
* must contain the PEM or DER representations of one or more X.509
* certificates. If a file contains multiple certificates, all certificates
@@ -446,6 +489,32 @@
public static byte[] readPrivateKey(@NotNull final File file)
throws LDAPException
{
+ return readPrivateKey(file, null);
+ }
+
+
+
+ /**
+ * Reads a PKCS #8 private key from the given file. The file must contain the
+ * PEM or DER representation of a single private key.
+ *
+ * @param file The file from which the private key should be
+ * read. It must not be {@code null}.
+ * @param encryptionPassword The password needed to decrypt the private key
+ * if it is encrypted. It may be {@code null} if
+ * the private key is not encrypted.
+ *
+ * @return The encoded representation of the PKCS #8 private key that was
+ * read.
+ *
+ * @throws LDAPException If a problem occurs while trying to read from
+ * or parse the content of the specified file.
+ */
+ @NotNull()
+ public static byte[] readPrivateKey(@NotNull final File file,
+ @Nullable final char[] encryptionPassword)
+ throws LDAPException
+ {
Validator.ensureNotNull(file,
"CertificateDataReplaceCertificateKeyStoreContent." +
"readPrivateKey.file must not be null.");
@@ -477,7 +546,7 @@
// Assume that the file is PEM-formatted.
- return readPEMPrivateKey(file, bis);
+ return readPEMPrivateKey(file, bis, encryptionPassword);
}
catch (final IOException e)
{
@@ -540,10 +609,13 @@
/**
* Reads a PEM-formatted PKCS #8 private key from the provided input stream.
*
- * @param file The file with which the provided input stream is
- * associated. It must not be {@code null}.
- * @param inputStream The input stream from which the private key will be
- * read. It must not be {@code null}.
+ * @param file The file with which the provided input stream
+ * is associated. It must not be {@code null}.
+ * @param inputStream The input stream from which the private key
+ * will be read. It must not be {@code null}.
+ * @param encryptionPassword The password needed to decrypt the private key
+ * if it is encrypted. It may be {@code null} if
+ * the private key is not encrypted.
*
* @return The bytes that comprise the encoded PKCS #8 private key.
*
@@ -556,13 +628,14 @@
@NotNull()
private static byte[] readPEMPrivateKey(
@NotNull final File file,
- @NotNull final InputStream inputStream)
+ @NotNull final InputStream inputStream,
+ @Nullable final char[] encryptionPassword)
throws IOException, LDAPException
{
try (PKCS8PEMFileReader pemReader = new PKCS8PEMFileReader(inputStream))
{
final PKCS8PrivateKey privateKey = pemReader.readPrivateKey();
- if (pemReader.readPrivateKey() != null)
+ if (pemReader.readPrivateKey(encryptionPassword) != null)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_CD_KSC_DECODE_MULTIPLE_PEM_KEYS_IN_FILE.get(
Modified: trunk/src/com/unboundid/util/ssl/PEMFileKeyManager.java
===================================================================
--- trunk/src/com/unboundid/util/ssl/PEMFileKeyManager.java 2022-12-08 17:49:08 UTC (rev 1579)
+++ trunk/src/com/unboundid/util/ssl/PEMFileKeyManager.java 2022-12-09 21:21:56 UTC (rev 1580)
@@ -114,28 +114,24 @@
/**
* Creates a new instance of this key manager with the provided PEM files.
*
- * @param certificateChainPEMFile The file containing the PEM-formatted
- * X.509 representations of the certificates
- * in the certificate chain. This must not
- * be {@code null}, the file must exist, and
- * it must contain at least one certificate
- * (the end entity certificate), but may
- * contain additional certificates as needed
- * for the complete certificate chain.
- * Certificates should be ordered such that
- * the first certificate must be the end
- * entity certificate, and each subsequent
- * certificate must be the issuer for the
- * previous certificate. The chain does not
- * need to be complete as long as the peer
- * may be expected to have prior knowledge of
- * any missing issuer certificates.
- * @param privateKeyPEMFile The file containing the PEM-formatted
- * PKCS #8 representation of the private key
- * for the end entity certificate. This must
- * not be {@code null}, the file must exist,
- * and it must contain exactly one
- * PEM-encoded private key.
+ * @param certificateChainPEMFile
+ * The file containing the PEM-formatted X.509 representations of
+ * the certificates in the certificate chain. This must not be
+ * {@code null}, the file must exist, and it must contain at
+ * least one certificate (the end entity certificate), but may
+ * contain additional certificates as needed for the complete
+ * certificate chain. Certificates should be ordered such that
+ * the first certificate must be the end entity certificate, and
+ * each subsequent certificate must be the issuer for the
+ * previous certificate. The chain does not need to be complete
+ * as long as the peer may be expected to have prior knowledge of
+ * any missing issuer certificates.
+ * @param privateKeyPEMFile
+ * The file containing the PEM-formatted PKCS #8 representation
+ * of the private key for the end entity certificate. This must
+ * not be {@code null}, the file must exist, and it must contain
+ * exactly one PEM-encoded private key. The private key must not
+ * be encrypted.
*
* @throws KeyStoreException If there is a problem with any of the provided
* PEM files.
@@ -152,31 +148,67 @@
/**
* Creates a new instance of this key manager with the provided PEM files.
*
- * @param certificateChainPEMFiles The files containing the PEM-formatted
- * X.509 representations of the certificates
- * in the certificate chain. This must not
- * be {@code null} or empty. Each file must
- * exist and must contain at least one
- * certificate. The files will be processed
- * in the order in which they are provided.
- * The first certificate in the first file
- * must be the end entity certificate, and
- * each subsequent certificate must be the
- * issuer for the previous certificate. The
- * chain does not need to be complete as
- * long as the peer may be expected to have
- * prior knowledge of any missing issuer
- * certificates.
- * @param privateKeyPEMFile The file containing the PEM-formatted
- * PKCS #8 representation of the private key
- * for the end entity certificate. This
- * must not be {@code null}, the file must
- * exist, and it must contain exactly one
- * PEM-encoded private key.
+ * @param certificateChainPEMFile
+ * The file containing the PEM-formatted X.509 representations of
+ * the certificates in the certificate chain. This must not be
+ * {@code null}, the file must exist, and it must contain at
+ * least one certificate (the end entity certificate), but may
+ * contain additional certificates as needed for the complete
+ * certificate chain. Certificates should be ordered such that
+ * the first certificate must be the end entity certificate, and
+ * each subsequent certificate must be the issuer for the
+ * previous certificate. The chain does not need to be complete
+ * as long as the peer may be expected to have prior knowledge of
+ * any missing issuer certificates.
+ * @param privateKeyPEMFile
+ * The file containing the PEM-formatted PKCS #8 representation
+ * of the private key for the end entity certificate. This must
+ * not be {@code null}, the file must exist, and it must contain
+ * exactly one PEM-encoded private key. The private key may
+ * optionally be encrypted.
+ * @param privateKeyEncryptionPassword
+ * The password needed to decrypt the private key if it is
+ * encrypted. This may be {@code null} if the private key is not
+ * encrypted.
*
* @throws KeyStoreException If there is a problem with any of the provided
* PEM files.
*/
+ public PEMFileKeyManager(@NotNull final File certificateChainPEMFile,
+ @NotNull final File privateKeyPEMFile,
+ @Nullable final char[] privateKeyEncryptionPassword)
+ throws KeyStoreException
+ {
+ this(Collections.singletonList(certificateChainPEMFile), privateKeyPEMFile,
+ privateKeyEncryptionPassword);
+ }
+
+
+
+ /**
+ * Creates a new instance of this key manager with the provided PEM files.
+ *
+ * @param certificateChainPEMFiles
+ * The files containing the PEM-formatted X.509 representations
+ * of the certificates in the certificate chain. This must not
+ * be {@code null} or empty. Each file must exist and must
+ * contain at least one certificate. The files will be processed
+ * in the order in which they are provided. The first
+ * certificate in the first file must be the end entity
+ * certificate, and each subsequent certificate must be the
+ * issuer for the previous certificate. The chain does not need
+ * to be complete as long as the peer may be expected to have
+ * prior knowledge of any missing issuer certificates.
+ * @param privateKeyPEMFile
+ * The file containing the PEM-formatted PKCS #8 representation
+ * of the private key for the end entity certificate. This must
+ * not be {@code null}, the file must exist, and it must contain
+ * exactly one PEM-encoded private key. The private key must not
+ * be encrypted.
+ *
+ * @throws KeyStoreException If there is a problem with any of the provided
+ * PEM files.
+ */
public PEMFileKeyManager(@NotNull final File[] certificateChainPEMFiles,
@NotNull final File privateKeyPEMFile)
throws KeyStoreException
@@ -189,35 +221,108 @@
/**
* Creates a new instance of this key manager with the provided PEM files.
*
- * @param certificateChainPEMFiles The files containing the PEM-formatted
- * X.509 representations of the certificates
- * in the certificate chain. This must not
- * be {@code null} or empty. Each file must
- * exist and must contain at least one
- * certificate. The files will be processed
- * in the order in which they are provided.
- * The first certificate in the first file
- * must be the end entity certificate, and
- * each subsequent certificate must be the
- * issuer for the previous certificate. The
- * chain does not need to be complete as
- * long as the peer may be expected to have
- * prior knowledge of any missing issuer
- * certificates.
- * @param privateKeyPEMFile The file containing the PEM-formatted
- * PKCS #8 representation of the private key
- * for the end entity certificate. This
- * must not be {@code null}, the file must
- * exist, and it must contain exactly one
- * PEM-encoded private key.
+ * @param certificateChainPEMFiles
+ * The files containing the PEM-formatted X.509 representations
+ * of the certificates in the certificate chain. This must not
+ * be {@code null} or empty. Each file must exist and must
+ * contain at least one certificate. The files will be processed
+ * in the order in which they are provided. The first
+ * certificate in the first file must be the end entity
+ * certificate, and each subsequent certificate must be the
+ * issuer for the previous certificate. The chain does not need
+ * to be complete as long as the peer may be expected to have
+ * prior knowledge of any missing issuer certificates.
+ * @param privateKeyPEMFile
+ * The file containing the PEM-formatted PKCS #8 representation
+ * of the private key for the end entity certificate. This must
+ * not be {@code null}, the file must exist, and it must contain
+ * exactly one PEM-encoded private key. The private key may
+ * optionally be encrypted.
+ * @param privateKeyEncryptionPassword
+ * The password needed to decrypt the private key if it is
+ * encrypted. This may be {@code null} if the private key is not
+ * encrypted.
*
* @throws KeyStoreException If there is a problem with any of the provided
* PEM files.
*/
+ public PEMFileKeyManager(@NotNull final File[] certificateChainPEMFiles,
+ @NotNull final File privateKeyPEMFile,
+ @Nullable final char[] privateKeyEncryptionPassword)
+ throws KeyStoreException
+ {
+ this(StaticUtils.toList(certificateChainPEMFiles), privateKeyPEMFile,
+ privateKeyEncryptionPassword);
+ }
+
+
+
+ /**
+ * Creates a new instance of this key manager with the provided PEM files.
+ *
+ * @param certificateChainPEMFiles
+ * The files containing the PEM-formatted X.509 representations
+ * of the certificates in the certificate chain. This must not
+ * be {@code null} or empty. Each file must exist and must
+ * contain at least one certificate. The files will be processed
+ * in the order in which they are provided. The first
+ * certificate in the first file must be the end entity
+ * certificate, and each subsequent certificate must be the
+ * issuer for the previous certificate. The chain does not need
+ * to be complete as long as the peer may be expected to have
+ * prior knowledge of any missing issuer certificates.
+ * @param privateKeyPEMFile
+ * The file containing the PEM-formatted PKCS #8 representation
+ * of the private key for the end entity certificate. This must
+ * not be {@code null}, the file must exist, and it must contain
+ * exactly one PEM-encoded private key. The private key must not
+ * be encrypted.
+ *
+ * @throws KeyStoreException If there is a problem with any of the provided
+ * PEM files.
+ */
public PEMFileKeyManager(@NotNull final List<File> certificateChainPEMFiles,
@NotNull final File privateKeyPEMFile)
throws KeyStoreException
{
+ this(certificateChainPEMFiles, privateKeyPEMFile, null);
+ }
+
+
+
+ /**
+ * Creates a new instance of this key manager with the provided PEM files.
+ *
+ * @param certificateChainPEMFiles
+ * The files containing the PEM-formatted X.509 representations
+ * of the certificates in the certificate chain. This must not
+ * be {@code null} or empty. Each file must exist and must
+ * contain at least one certificate. The files will be processed
+ * in the order in which they are provided. The first
+ * certificate in the first file must be the end entity
+ * certificate, and each subsequent certificate must be the
+ * issuer for the previous certificate. The chain does not need
+ * to be complete as long as the peer may be expected to have
+ * prior knowledge of any missing issuer certificates.
+ * @param privateKeyPEMFile
+ * The file containing the PEM-formatted PKCS #8 representation
+ * of the private key for the end entity certificate. This must
+ * not be {@code null}, the file must exist, and it must contain
+ * exactly one PEM-encoded private key. The private key may
+ * optionally be encrypted.
+ * @param privateKeyEncryptionPassword
+ * The password needed to decrypt the private key if it is
+ * encrypted. This may be {@code null} if the private key is not
+ * encrypted.
+ *
+ * @throws KeyStoreException If there is a problem with any of the provided
+ * PEM files.
+ */
+ public PEMFileKeyManager(@NotNull final List<File> certificateChainPEMFiles,
+ @NotNull final File privateKeyPEMFile,
+ @Nullable final char[] privateKeyEncryptionPassword)
+ throws KeyStoreException
+ {
Validator.ensureNotNullWithMessage(certificateChainPEMFiles,
"PEMFileKeyManager.certificateChainPEMFiles must not be null.");
Validator.ensureFalse(certificateChainPEMFiles.isEmpty(),
@@ -226,7 +331,8 @@
"PEMFileKeyManager.privateKeyPEMFile must not be null.");
certificateChain = readCertificateChain(certificateChainPEMFiles);
- privateKey = readPrivateKey(privateKeyPEMFile);
+ privateKey = readPrivateKey(privateKeyPEMFile,
+ privateKeyEncryptionPassword);
// Compute a SHA-256 fingerprint for the certificate to use as the alias.
@@ -370,12 +476,16 @@
* Reads the private key from the provided PEM file.
*
*
- * @param privateKeyPEMFile The file containing the PEM-formatted
- * PKCS #8 representation of the private key
- * for the end entity certificate. This
- * must not be {@code null}, the file must
- * exist, and it must contain exactly one
- * PEM-encoded private key.
+ * @param privateKeyPEMFile
+ * The file containing the PEM-formatted PKCS #8 representation
+ * of the private key for the end entity certificate. This must
+ * not be {@code null}, the file must exist, and it must contain
+ * exactly one PEM-encoded private key. The private key may
+ * optionally be encrypted.
+ * @param encryptionPassword
+ * The password needed to decrypt the private key if it is
+ * encrypted. This may be {@code null} if the private key is not
+ * encrypted.
*
* @return The private key that was read.
*
@@ -384,7 +494,8 @@
*/
@NotNull()
private static PrivateKey readPrivateKey(
- @NotNull final File privateKeyPEMFile)
+ @NotNull final File privateKeyPEMFile,
+ @Nullable final char[] encryptionPassword)
throws KeyStoreException
{
if (! privateKeyPEMFile.exists())
@@ -396,7 +507,7 @@
try (PKCS8PEMFileReader r = new PKCS8PEMFileReader(privateKeyPEMFile))
{
- final PKCS8PrivateKey privateKey = r.readPrivateKey();
+ final PKCS8PrivateKey privateKey = r.readPrivateKey(encryptionPassword);
if (privateKey == null)
{
throw new KeyStoreException(
@@ -404,7 +515,7 @@
privateKeyPEMFile.getAbsolutePath()));
}
- if (r.readPrivateKey() != null)
+ if (r.readPrivateKey(encryptionPassword) != null)
{
throw new KeyStoreException(
ERR_PEM_FILE_KEY_MANAGER_MULTIPLE_KEYS_IN_FILE.get(
Modified: trunk/src/com/unboundid/util/ssl/cert/ManageCertificates.java
===================================================================
--- trunk/src/com/unboundid/util/ssl/cert/ManageCertificates.java 2022-12-08 17:49:08 UTC (rev 1579)
+++ trunk/src/com/unboundid/util/ssl/cert/ManageCertificates.java 2022-12-09 21:21:56 UTC (rev 1580)
@@ -1004,6 +1004,28 @@
exportKeyOutputFile.addLongIdentifier("filename", true);
exportKeyParser.addArgument(exportKeyOutputFile);
+ final StringArgument exportKeyEncryptionPassword = new StringArgument(null,
+ "encryption-password", false, 1,
+ INFO_MANAGE_CERTS_PLACEHOLDER_PASSWORD.get(),
+ INFO_MANAGE_CERTS_SC_EXPIRT_KEY_ARG_ENC_PW_DESC.get());
+ exportKeyEncryptionPassword.addLongIdentifier("encryptionPassword", true);
+ exportKeyParser.addArgument(exportKeyEncryptionPassword);
+
+ final FileArgument exportKeyEncryptionPasswordFile = new FileArgument(null,
+ "encryption-password-file", false, 1, null,
+ INFO_MANAGE_CERTS_SC_EXPORT_KEY_ARG_ENC_PW_FILE_DESC.get(), true, true,
+ true, false);
+ exportKeyEncryptionPasswordFile.addLongIdentifier("encryptionPasswordFile",
+ true);
+ exportKeyParser.addArgument(exportKeyEncryptionPasswordFile);
+
+ final BooleanArgument exportKeyPromptForEncryptionPassword =
+ new BooleanArgument(null, "prompt-for-encryption-password", 1,
+ INFO_MANAGE_CERTS_SC_EXPORT_KEY_ARG_PROMPT_FOR_ENC_PW.get());
+ exportKeyPromptForEncryptionPassword.addLongIdentifier(
+ "promptForEncryptionPassword", true);
+ exportKeyParser.addArgument(exportKeyPromptForEncryptionPassword);
+
exportKeyParser.addRequiredArgumentSet(exportKeyKeystorePassword,
exportKeyKeystorePasswordFile, exportKeyPromptForKeystorePassword);
exportKeyParser.addExclusiveArgumentSet(exportKeyKeystorePassword,
@@ -1010,6 +1032,8 @@
exportKeyKeystorePasswordFile, exportKeyPromptForKeystorePassword);
exportKeyParser.addExclusiveArgumentSet(exportKeyPKPassword,
exportKeyPKPasswordFile, exportKeyPromptForPKPassword);
+ exportKeyParser.addExclusiveArgumentSet(exportKeyEncryptionPassword,
+ exportKeyEncryptionPasswordFile, exportKeyPromptForEncryptionPassword);
final LinkedHashMap<String[],String> exportKeyExamples =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(2));
@@ -1213,6 +1237,29 @@
importCertPromptForPKPassword.addLongIdentifier("promptForKeyPIN", true);
importCertParser.addArgument(importCertPromptForPKPassword);
+ final StringArgument importCertEncryptionPassword = new StringArgument(null,
+ "encryption-password", false, 1,
+ INFO_MANAGE_CERTS_PLACEHOLDER_PASSWORD.get(),
+ INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_ENC_PW_DESC.get());
+ importCertEncryptionPassword.addLongIdentifier("encryptionPassword", true);
+ importCertEncryptionPassword.setSensitive(true);
+ importCertParser.addArgument(importCertEncryptionPassword);
+
+ final FileArgument importCertEncryptionPasswordFile = new FileArgument(null,
+ "encryption-password-file", false, 1, null,
+ INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_ENC_PW_FILE_DESC.get(), true,
+ true, true, false);
+ importCertEncryptionPasswordFile.addLongIdentifier("encryptionPasswordFile",
+ true);
+ importCertParser.addArgument(importCertEncryptionPasswordFile);
+
+ final BooleanArgument importCertPromptForEncryptionPassword =
+ new BooleanArgument(null, "prompt-for-encryption-password",
+ INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_PROMPT_FOR_ENC_PW_DESC.get());
+ importCertPromptForEncryptionPassword.addLongIdentifier(
+ "promptForEncryptionPassword", true);
+ importCertParser.addArgument(importCertPromptForEncryptionPassword);
+
final BooleanArgument importCertNoPrompt = new BooleanArgument(null,
"no-prompt", 1,
INFO_MANAGE_CERTS_SC_IMPORT_CERT_ARG_NO_PROMPT_DESC.get());
@@ -1233,6 +1280,9 @@
importCertKeystorePasswordFile, importCertPromptForKeystorePassword);
importCertParser.addExclusiveArgumentSet(importCertPKPassword,
importCertPKPasswordFile, importCertPromptForPKPassword);
+ importCertParser.addExclusiveArgumentSet(importCertEncryptionPassword,
+ importCertEncryptionPasswordFile,
+ importCertPromptForEncryptionPassword);
final LinkedHashMap<String[],String> importCertExamples =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(2));
@@ -5388,6 +5438,20 @@
}
+ // Get the password to use to encrypt the private key when it is exported.
+ final char[] encryptionPassword;
+ try
+ {
+ encryptionPassword = getPrivateKeyEncryptionPassword(false);
+ }
+ catch (final LDAPException le)
+ {
+ Debug.debugException(le);
+ wrapErr(0, WRAP_COLUMN, le.getMessage());
+ return le.getResultCode();
+ }
+
+
// Get the private key to export.
final PrivateKey privateKey;
try
@@ -5451,11 +5515,23 @@
{
if (exportPEM)
{
- writePEMPrivateKey(printStream, privateKey.getEncoded());
+ writePEMPrivateKey(printStream, privateKey.getEncoded(),
+ encryptionPassword);
}
else
{
- printStream.write(privateKey.getEncoded());
+ if (encryptionPassword == null)
+ {
+ printStream.write(privateKey.getEncoded());
+ }
+ else
+ {
+ final byte[] encryptedPrivateKey =
+ PKCS8EncryptionHandler.encryptPrivateKey(
+ privateKey.getEncoded(), encryptionPassword,
+ new PKCS8EncryptionProperties());
+ printStream.write(encryptedPrivateKey);
+ }
}
}
catch (final Exception e)
@@ -5577,6 +5653,21 @@
}
+ // See if there is a password to use to decrypt the private key that is
+ // being imported.
+ final char[] encryptionPassword;
+ try
+ {
+ encryptionPassword = getPrivateKeyEncryptionPassword(true);
+ }
+ catch (final LDAPException le)
+ {
+ Debug.debugException(le);
+ wrapErr(0, WRAP_COLUMN, le.getMessage());
+ return le.getResultCode();
+ }
+
+
// If a private key file was specified, then read the private key.
final PKCS8PrivateKey privateKey;
if (privateKeyFile == null)
@@ -5587,7 +5678,7 @@
{
try
{
- privateKey = readPrivateKeyFromFile(privateKeyFile);
+ privateKey = readPrivateKeyFromFile(privateKeyFile, encryptionPassword);
}
catch (final LDAPException le)
{
@@ -11040,23 +11131,57 @@
* Writes a PEM-encoded representation of the provided encoded private key to
* the given print stream.
*
- * @param printStream The print stream to which the PEM-encoded
- * private key should be written. It must not be
- * {@code null}.
- * @param encodedPrivateKey The bytes that comprise the encoded private key.
- * It must not be {@code null}.
+ * @param printStream The print stream to which the PEM-encoded
+ * private key should be written. It must not be
+ * {@code null}.
+ * @param encodedPrivateKey The bytes that comprise the encoded private
+ * key. It must not be {@code null}.
+ * @param encryptionPassword The password to use to encrypt the private key.
+ * It may be {@code null} if the private key
+ * should not be encrypted.
+ *
+ * @throws LDAPException If a problem occurs while trying write the private
+ * key.
*/
private static void writePEMPrivateKey(
@NotNull final PrintStream printStream,
- @NotNull final byte[] encodedPrivateKey)
+ @NotNull final byte[] encodedPrivateKey,
+ @Nullable final char[] encryptionPassword)
+ throws LDAPException
{
- final String certBase64 = Base64.encode(encodedPrivateKey);
- printStream.println("-----BEGIN PRIVATE KEY-----");
- for (final String line : StaticUtils.wrapLine(certBase64, 64))
+ if (encryptionPassword == null)
{
- printStream.println(line);
+ final String certBase64 = Base64.encode(encodedPrivateKey);
+ printStream.println("-----BEGIN PRIVATE KEY-----");
+ for (final String line : StaticUtils.wrapLine(certBase64, 64))
+ {
+ printStream.println(line);
+ }
+ printStream.println("-----END PRIVATE KEY-----");
}
- printStream.println("-----END PRIVATE KEY-----");
+ else
+ {
+ final byte[] encryptedPrivateKey;
+ try
+ {
+ encryptedPrivateKey = PKCS8EncryptionHandler.encryptPrivateKey(
+ encodedPrivateKey, encryptionPassword,
+ new PKCS8EncryptionProperties());
+ }
+ catch (final CertException e)
+ {
+ Debug.debugException(e);
+ throw new LDAPException(ResultCode.LOCAL_ERROR, e.getMessage(), e);
+ }
+
+ final String encryptedCertBase64 = Base64.encode(encryptedPrivateKey);
+ printStream.println("-----BEGIN ENCRYPTED PRIVATE KEY-----");
+ for (final String line : StaticUtils.wrapLine(encryptedCertBase64, 64))
+ {
+ printStream.println(line);
+ }
+ printStream.println("-----END ENCRYPTED PRIVATE KEY-----");
+ }
}
@@ -11329,6 +11454,99 @@
/**
+ * Retrieves the password used to encrypt a private key.
+ *
+ * @param forDecrypt Indicates whether the password will be used to decrypt
+ * the private key rather than to encrypt it.
+ *
+ * @return The password used to encrypt a private key, or {@code null} if no
+ * encryption password was specified.
+ *
+ * @throws LDAPException If a problem is encountered while trying to get the
+ * keystore password.
+ */
+ @Nullable()
+ private char[] getPrivateKeyEncryptionPassword(final boolean forDecrypt)
+ throws LDAPException
+ {
+ final StringArgument passwordArgument =
+ subCommandParser.getStringArgument("encryption-password");
+ if ((passwordArgument != null) && passwordArgument.isPresent())
+ {
+ return passwordArgument.getValue().toCharArray();
+ }
+
+
+ final FileArgument passwordFileArgument =
+ subCommandParser.getFileArgument("encryption-password-file");
+ if ((passwordFileArgument != null) && passwordFileArgument.isPresent())
+ {
+ final File f = passwordFileArgument.getValue();
+ try
+ {
+ return getPasswordFileReader().readPassword(f);
+ }
+ catch (final LDAPException e)
+ {
+ Debug.debugException(e);
+ throw e;
+ }
+ catch (final Exception e)
+ {
+ Debug.debugException(e);
+ throw new LDAPException(ResultCode.LOCAL_ERROR,
+ ERR_MANAGE_CERTS_GET_PK_ENC_PW_ERROR_READING_FILE.get(
+ f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
+ e);
+ }
+ }
+
+
+ final BooleanArgument promptArgument = subCommandParser.getBooleanArgument(
+ "prompt-for-encryption-password");
+ if ((promptArgument != null) && promptArgument.isPresent())
+ {
+ out();
+ if (forDecrypt)
+ {
+ // We'll be decrypting an existing private key, so we only need to
+ // prompt for the password once.
+ return promptForPassword(
+ INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_DECRYPT.get(), false);
+ }
+ else
+ {
+ // We'll be encrypting the private key, so we need to prompt twice to
+ // confirm that the entered password is what the user intended.
+ while (true)
+ {
+ final char[] pwChars = promptForPassword(
+ INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_ENCRYPT_1.get(), false);
+ final char[] confirmChars = promptForPassword(
+ INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_ENCRYPT_2.get(), true);
+
+ if (Arrays.equals(pwChars, confirmChars))
+ {
+ Arrays.fill(confirmChars, '\u0000');
+ return pwChars;
+ }
+ else
+ {
+ wrapErr(0, WRAP_COLUMN,
+ INFO_MANAGE_CERTS_GET_PK_ENC_PW_PROMPT_MISMATCH.get());
+ err();
+ }
+ }
+ }
+ }
+
+
+ return null;
+ }
+
+
+
+ /**
* Prompts for a password and retrieves the value that the user entered.
*
* @param prompt The prompt to display to the user.
@@ -12163,8 +12381,11 @@
* Reads a private key from the specified file. The file must exist and must
* contain exactly one PEM-encoded or DER-encoded PKCS #8 private key.
*
- * @param f The path to the private key file to read. It must not be
- * {@code null}.
+ * @param f The path to the private key file to read. It
+ * must not be {@code null}.
+ * @param encryptionPassword The password to use to encrypt the private key.
+ * It may be {@code null} if the private key is
+ * not encrypted.
*
* @return The private key read from the file.
*
@@ -12172,7 +12393,8 @@
* private key.
*/
@NotNull()
- static PKCS8PrivateKey readPrivateKeyFromFile(@NotNull final File f)
+ static PKCS8PrivateKey readPrivateKeyFromFile(@NotNull final File f,
+ @Nullable final char[] encryptionPassword)
throws LDAPException
{
// Read the first byte of the file to see if it contains DER-formatted data,
@@ -12234,7 +12456,15 @@
{
try
{
- privateKey = new PKCS8PrivateKey(pkElement.encode());
+ if (encryptionPassword == null)
+ {
+ privateKey = new PKCS8PrivateKey(pkElement.encode());
+ }
+ else
+ {
+ privateKey = PKCS8EncryptionHandler.decryptPrivateKey(
+ pkElement.encode(), encryptionPassword);
+ }
}
catch (final Exception e)
{
@@ -12260,6 +12490,7 @@
{
boolean inKey = false;
boolean isRSAKey = false;
+ boolean isEncryptedKey = false;
final StringBuilder buffer = new StringBuilder();
while (true)
{
@@ -12292,7 +12523,8 @@
}
if (line.equals("-----BEGIN PRIVATE KEY-----") ||
- line.equals("-----BEGIN RSA PRIVATE KEY-----"))
+ line.equals("-----BEGIN RSA PRIVATE KEY-----") ||
+ line.equals("-----BEGIN ENCRYPTED PRIVATE KEY-----"))
{
if (inKey)
{
@@ -12313,10 +12545,21 @@
{
isRSAKey = true;
}
+ else if (line.equals("-----BEGIN ENCRYPTED PRIVATE KEY-----"))
+ {
+ isEncryptedKey = true;
+ if (encryptionPassword == null)
+ {
+ throw new LDAPException(ResultCode.PARAM_ERROR,
+ ERR_MANAGE_CERTS_READ_PK_FROM_FILE_PK_ENCRYPTED_NO_PW.
+ get(f.getAbsolutePath()));
+ }
+ }
}
}
else if (line.equals("-----END PRIVATE KEY-----") ||
- line.equals("-----END RSA PRIVATE KEY-----"))
+ line.equals("-----END RSA PRIVATE KEY-----") ||
+ line.equals("-----END ENCRYPTED PRIVATE KEY-----"))
{
if (! inKey)
{
@@ -12348,7 +12591,15 @@
try
{
- privateKey = new PKCS8PrivateKey(pkBytes);
+ if (isEncryptedKey)
+ {
+ privateKey = PKCS8EncryptionHandler.
+ decryptPrivateKey(pkBytes, encryptionPassword);
+ }
+ else
+ {
+ privateKey = new PKCS8PrivateKey(pkBytes);
+ }
}
catch (final CertException e)
{
Added: trunk/src/com/unboundid/util/ssl/cert/PKCS5AlgorithmIdentifier.java
===================================================================
--- trunk/src/com/unboundid/util/ssl/cert/PKCS5AlgorithmIdentifier.java (rev 0)
+++ trunk/src/com/unboundid/util/ssl/cert/PKCS5AlgorithmIdentifier.java 2022-12-09 21:21:56 UTC (rev 1580)
@@ -0,0 +1,574 @@
+/*
+ * Copyright 2022 Ping Identity Corporation
+ * All Rights Reserved.
+ */
+/*
+ * Copyright 2022 Ping Identity Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Copyright (C) 2022 Ping Identity Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (GPLv2 only)
+ * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
+ */
+package com.unboundid.util.ssl.cert;
+
...
[truncated message content] |