[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] |