001/* 002 * Copyright 2022-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2022-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2022-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util.ssl.cert; 037 038 039 040import javax.crypto.Cipher; 041import javax.crypto.SecretKey; 042import javax.crypto.SecretKeyFactory; 043import javax.crypto.spec.IvParameterSpec; 044import javax.crypto.spec.PBEKeySpec; 045import javax.crypto.spec.SecretKeySpec; 046 047import com.unboundid.asn1.ASN1Constants; 048import com.unboundid.asn1.ASN1Element; 049import com.unboundid.asn1.ASN1Integer; 050import com.unboundid.asn1.ASN1Null; 051import com.unboundid.asn1.ASN1ObjectIdentifier; 052import com.unboundid.asn1.ASN1OctetString; 053import com.unboundid.asn1.ASN1Sequence; 054import com.unboundid.util.CryptoHelper; 055import com.unboundid.util.Debug; 056import com.unboundid.util.NotNull; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060 061import static com.unboundid.util.ssl.cert.CertMessages.*; 062 063 064 065/** 066 * This class provides a set of utility methods for interacting with encrypted 067 * PKCS #8 private keys. 068 */ 069@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 070public final class PKCS8EncryptionHandler 071{ 072 /** 073 * Prevents this utility class from being instantiated. 074 */ 075 private PKCS8EncryptionHandler() 076 { 077 // No implementation is required. 078 } 079 080 081 082 /** 083 * Encrypts the provided PKCS #8 private key using the provided settings. 084 * 085 * @param privateKey The private key to encrypt. It must not be 086 * {@code null}. 087 * @param encryptionPassword The password to use to generate the 088 * encryption key. It must not be {@code null}. 089 * @param encryptionProperties The properties to use when encrypting the 090 * key. It must not be {@code null}. 091 * 092 * @return The bytes that contain the DER-encoded encrypted representation of 093 * the private key. 094 * 095 * @throws CertException If a problem occurs while attempting to encrypt the 096 * provided certificate with the given settings. 097 */ 098 @NotNull() 099 public static byte[] encryptPrivateKey( 100 @NotNull final PKCS8PrivateKey privateKey, 101 @NotNull final char[] encryptionPassword, 102 @NotNull final PKCS8EncryptionProperties encryptionProperties) 103 throws CertException 104 { 105 return encryptPrivateKey(privateKey.getPKCS8PrivateKeyBytes(), 106 encryptionPassword, encryptionProperties); 107 } 108 109 110 111 /** 112 * Encrypts the provided PKCS #8 private key using the provided settings. 113 * 114 * @param privateKeyBytes The bytes that comprise the private key to 115 * encrypt. It must not be {@code null}. 116 * @param encryptionPassword The password to use to generate the 117 * encryption key. It must not be {@code null}. 118 * @param encryptionProperties The properties to use when encrypting the 119 * key. It must not be {@code null}. 120 * 121 * @return The bytes that contain the DER-encoded encrypted representation of 122 * the private key. 123 * 124 * @throws CertException If a problem occurs while attempting to encrypt the 125 * provided certificate with the given settings. 126 */ 127 @NotNull() 128 public static byte[] encryptPrivateKey( 129 @NotNull final byte[] privateKeyBytes, 130 @NotNull final char[] encryptionPassword, 131 @NotNull final PKCS8EncryptionProperties encryptionProperties) 132 throws CertException 133 { 134 final PKCS5AlgorithmIdentifier keyFactoryPRFAlgorithm = 135 encryptionProperties.getKeyFactoryPRFAlgorithm(); 136 final int keyFactoryIterationCount = 137 encryptionProperties.getKeyFactoryIterationCount(); 138 final int keyFactorySaltLengthBytes = 139 encryptionProperties.getKeyFactorySaltLengthBytes(); 140 final PKCS5AlgorithmIdentifier cipherTransformationAlgorithm = 141 encryptionProperties.getCipherTransformationAlgorithm(); 142 143 final String keyFactoryAlgorithm = PKCS5AlgorithmIdentifier. 144 getPBKDF2SecretKeyFactoryAlgorithmForPseudorandomFunction( 145 keyFactoryPRFAlgorithm); 146 final String cipherAlgorithm = 147 PKCS5AlgorithmIdentifier.getCipherAlgorithmName( 148 cipherTransformationAlgorithm); 149 final String cipherTransformation = 150 PKCS5AlgorithmIdentifier.getCipherTransformationName( 151 cipherTransformationAlgorithm); 152 final int cipherKeyLengthBits = 153 PKCS5AlgorithmIdentifier.getCipherKeySizeBits( 154 cipherTransformationAlgorithm); 155 156 157 // Generate the secret key. 158 final SecretKey secretKey; 159 final byte[] keyFactorySalt = 160 StaticUtils.randomBytes(keyFactorySaltLengthBytes, true); 161 try 162 { 163 final SecretKeyFactory keyFactory = 164 CryptoHelper.getSecretKeyFactory(keyFactoryAlgorithm); 165 final PBEKeySpec pbeKeySpec = new PBEKeySpec(encryptionPassword, 166 keyFactorySalt, keyFactoryIterationCount, cipherKeyLengthBits); 167 secretKey = new SecretKeySpec( 168 keyFactory.generateSecret(pbeKeySpec).getEncoded(), 169 cipherAlgorithm); 170 } 171 catch (final Exception e) 172 { 173 Debug.debugException(e); 174 throw new CertException( 175 ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_ENC_SECRET_KEY.get( 176 keyFactoryAlgorithm, StaticUtils.getExceptionMessage(e)), 177 e); 178 } 179 180 181 // Generate the cipher. 182 final Cipher cipher; 183 final byte[] cipherInitializationVector; 184 try 185 { 186 cipher = CryptoHelper.getCipher(cipherTransformation); 187 cipher.init(Cipher.ENCRYPT_MODE, secretKey); 188 cipherInitializationVector = cipher.getIV(); 189 } 190 catch (final Exception e) 191 { 192 Debug.debugException(e); 193 throw new CertException( 194 ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_ENC_CIPHER.get( 195 cipherTransformation, StaticUtils.getExceptionMessage(e)), 196 e); 197 } 198 199 200 // Encrypt the private key. 201 final byte[] encryptedPrivteKeyData; 202 try 203 { 204 encryptedPrivteKeyData = cipher.doFinal(privateKeyBytes); 205 } 206 catch (final Exception e) 207 { 208 Debug.debugException(e); 209 throw new CertException( 210 ERR_PKCS8_ENC_HANDLER_CANNOT_ENCRYPT_PRIVATE_KEY.get( 211 cipherTransformation, StaticUtils.getExceptionMessage(e)), 212 e); 213 } 214 215 216 // Create the and return DER representation of the encrypted key. 217 try 218 { 219 final ASN1Sequence kdfParametersSequence = new ASN1Sequence( 220 new ASN1OctetString(keyFactorySalt), 221 new ASN1Integer(keyFactoryIterationCount), 222 new ASN1Sequence( 223 new ASN1ObjectIdentifier(keyFactoryPRFAlgorithm.getOID()), 224 new ASN1Null())); 225 final ASN1Sequence kdfIdentifierSequence = new ASN1Sequence( 226 new ASN1ObjectIdentifier(PKCS5AlgorithmIdentifier.PBKDF2.getOID()), 227 kdfParametersSequence); 228 229 final ASN1Sequence cipherSequence = new ASN1Sequence( 230 new ASN1ObjectIdentifier(cipherTransformationAlgorithm.getOID()), 231 new ASN1OctetString(cipherInitializationVector)); 232 233 final ASN1Sequence pbes2ParametersSequence = new ASN1Sequence( 234 kdfIdentifierSequence, 235 cipherSequence); 236 237 final ASN1Sequence pbes2Sequence = new ASN1Sequence( 238 new ASN1ObjectIdentifier(PKCS5AlgorithmIdentifier.PBES2.getOID()), 239 pbes2ParametersSequence); 240 241 final ASN1Sequence encryptedPrivateKeySequence = new ASN1Sequence( 242 pbes2Sequence, 243 new ASN1OctetString(encryptedPrivteKeyData)); 244 245 return encryptedPrivateKeySequence.encode(); 246 } 247 catch (final Exception e) 248 { 249 Debug.debugException(e); 250 throw new CertException( 251 ERR_PKCS8_ENC_HANDLER_CANNOT_ENCODE_ENC_PRIVATE_KEY.get( 252 StaticUtils.getExceptionMessage(e)), 253 e); 254 } 255 } 256 257 258 259 /** 260 * Attempts to decrypt the provided data as a PKCS #8 private key. 261 * 262 * @param encryptedPrivateKeyBytes The bytes that comprise the encrypted 263 * representation of a PKCS #8 private key. 264 * It must not be {@code null}. 265 * @param encryptionPassword The password used to generate the 266 * encryption key. It must not be 267 * {@code null}. 268 * 269 * @return The decrypted and decoded PKCS #8 private key. 270 * 271 * @throws CertException If a problem occurs while attempting to decrypt the 272 * encrypted private key. 273 */ 274 @NotNull() 275 public static PKCS8PrivateKey decryptPrivateKey( 276 @NotNull final byte[] encryptedPrivateKeyBytes, 277 @NotNull final char[] encryptionPassword) 278 throws CertException 279 { 280 // Try to decode the private key bytes as an ASN.1 sequence of two elements. 281 final ASN1Sequence encryptedKeySequence; 282 try 283 { 284 encryptedKeySequence = 285 ASN1Sequence.decodeAsSequence(encryptedPrivateKeyBytes); 286 } 287 catch (final Exception e) 288 { 289 Debug.debugException(e); 290 throw new CertException( 291 ERR_PKCS8_ENC_HANDLER_CANNOT_PARSE_AS_ENC_KEY_SEQUENCE.get(), 292 e); 293 } 294 295 final ASN1Element[] encryptedKeyElements = encryptedKeySequence.elements(); 296 if (encryptedKeyElements.length != 2) 297 { 298 throw new CertException( 299 ERR_PKCS8_ENC_HANDLER_SEQUENCE_UNEXPECTED_ENC_KEY_ELEMENT_COUNT.get( 300 encryptedKeyElements.length)); 301 } 302 303 304 // The first element of the sequence should be the algorithm identifier for 305 // the encryption scheme. It should be a sequence containing two elements. 306 final ASN1Sequence keyEncryptionSchemeSequence; 307 try 308 { 309 keyEncryptionSchemeSequence = encryptedKeyElements[0].decodeAsSequence(); 310 } 311 catch (final Exception e) 312 { 313 Debug.debugException(e); 314 throw new CertException( 315 ERR_PKCS8_ENC_HANDLER_KEY_SCHEME_ELEMENT_NOT_SEQUENCE.get(), 316 e); 317 } 318 319 final ASN1Element[] keyEncryptionSchemeElements = 320 keyEncryptionSchemeSequence.elements(); 321 if (keyEncryptionSchemeElements.length != 2) 322 { 323 throw new CertException( 324 ERR_PKCS8_ENC_HANDLER_SEQUENCE_UNEXPECTED_KEY_SCHEME_ELEMENT_COUNT. 325 get(keyEncryptionSchemeElements.length)); 326 } 327 328 329 // The first element of the encryption scheme sequence should be the OID of 330 // the encryption scheme. This implementation only supports the PBES2 331 // scheme. 332 final ASN1ObjectIdentifier keyEncryptionSchemeOID; 333 try 334 { 335 keyEncryptionSchemeOID = 336 keyEncryptionSchemeElements[0].decodeAsObjectIdentifier(); 337 } 338 catch (final Exception e) 339 { 340 throw new CertException( 341 ERR_PKCS8_ENC_HANDLER_CANNOT_PARSE_KEY_SCHEME_OID.get(), e); 342 } 343 344 if (! keyEncryptionSchemeOID.getOID().equals( 345 PKCS5AlgorithmIdentifier.PBES2.getOID())) 346 { 347 throw new CertException( 348 ERR_PKCS8_ENC_HANDLER_ENC_SCHEME_NOT_PBES2.get( 349 keyEncryptionSchemeOID.getOID().toString())); 350 } 351 352 353 // The second element of the encryption scheme sequence should itself be a 354 // sequence containing two elements. 355 final ASN1Sequence pbes2Sequence; 356 try 357 { 358 pbes2Sequence = keyEncryptionSchemeElements[1].decodeAsSequence(); 359 } 360 catch (final Exception e) 361 { 362 Debug.debugException(e); 363 throw new CertException( 364 ERR_PKCS8_ENC_HANDLER_PBES2_PARAMS_NOT_SEQUENCE.get(), e); 365 } 366 367 final ASN1Element[] pbes2Elements = pbes2Sequence.elements(); 368 if (pbes2Elements.length != 2) 369 { 370 throw new CertException( 371 ERR_PKCS8_ENC_HANDLER_PBES2_UNEXPECTED_PARAMS_SEQUENCE_ELEMENT_COUNT. 372 get(pbes2Elements.length)); 373 } 374 375 376 // The first element of the PBES2 algorithm parameters sequence should be 377 // a sequence containing an OID and a sequence of parameters that describe 378 // the key derivation function to use. This implementation only supports 379 // the PBKDF2 key derivation function. 380 final String keyFactoryAlgorithm; 381 final byte[] keyFactorySalt; 382 final int keyFactoryIterationCount; 383 Integer encryptionKeyLength = null; 384 try 385 { 386 final ASN1Element[] kdfElements = 387 pbes2Elements[0].decodeAsSequence().elements(); 388 final ASN1ObjectIdentifier kdfOID = 389 kdfElements[0].decodeAsObjectIdentifier(); 390 if (! kdfOID.getOID().equals(PKCS5AlgorithmIdentifier.PBKDF2.getOID())) 391 { 392 throw new CertException(ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_KDF.get( 393 kdfOID.getOID().toString())); 394 } 395 396 final ASN1Element[] pbkdf2Elements = 397 kdfElements[1].decodeAsSequence().elements(); 398 keyFactorySalt = pbkdf2Elements[0].decodeAsOctetString().getValue(); 399 keyFactoryIterationCount = pbkdf2Elements[1].decodeAsInteger().intValue(); 400 401 PKCS5AlgorithmIdentifier prf = PKCS5AlgorithmIdentifier.HMAC_SHA_1; 402 for (int i=2; i < pbkdf2Elements.length; i++) 403 { 404 if (pbkdf2Elements[i].getType() == ASN1Constants.UNIVERSAL_INTEGER_TYPE) 405 { 406 encryptionKeyLength = pbkdf2Elements[i].decodeAsInteger().intValue(); 407 } 408 else if (pbkdf2Elements[i].getType() == 409 ASN1Constants.UNIVERSAL_SEQUENCE_TYPE) 410 { 411 final ASN1ObjectIdentifier prfOID = pbkdf2Elements[i]. 412 decodeAsSequence().elements()[0].decodeAsObjectIdentifier(); 413 prf = PKCS5AlgorithmIdentifier.forOID(prfOID.getOID()); 414 if (prf == null) 415 { 416 throw new CertException( 417 ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_PBKDF2_PRF.get( 418 prfOID.getOID().toString())); 419 } 420 } 421 } 422 423 keyFactoryAlgorithm = PKCS5AlgorithmIdentifier. 424 getPBKDF2SecretKeyFactoryAlgorithmForPseudorandomFunction(prf); 425 if (keyFactoryAlgorithm == null) 426 { 427 throw new CertException( 428 ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_PBKDF2_PRF.get( 429 prf.getOID().toString())); 430 } 431 } 432 catch (final CertException e) 433 { 434 Debug.debugException(e); 435 throw e; 436 } 437 catch (final Exception e) 438 { 439 Debug.debugException(e); 440 throw new CertException( 441 ERR_PKCS8_ENC_HANDLER_CANNOT_DECODE_KDF_SETTINGS.get( 442 StaticUtils.getExceptionMessage(e)), 443 e); 444 } 445 446 447 // The second element of the PBES2 algorithm parameters sequence should be a 448 // sequence containing an OID and an octet string with parameters for the 449 // encryption algorithm. 450 final String cipherAlgorithm; 451 final String cipherTransformation; 452 final byte[] initializationVector; 453 try 454 { 455 final ASN1Element[] cipherElements = 456 pbes2Elements[1].decodeAsSequence().elements(); 457 final ASN1ObjectIdentifier cipherOID = 458 cipherElements[0].decodeAsObjectIdentifier(); 459 final PKCS5AlgorithmIdentifier cipherIdentifier = 460 PKCS5AlgorithmIdentifier.forOID(cipherOID.getOID()); 461 if (cipherIdentifier == null) 462 { 463 throw new CertException( 464 ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_CIPHER.get( 465 cipherOID.getOID().toString())); 466 } 467 468 cipherAlgorithm = 469 PKCS5AlgorithmIdentifier.getCipherAlgorithmName(cipherIdentifier); 470 cipherTransformation = 471 PKCS5AlgorithmIdentifier.getCipherTransformationName( 472 cipherIdentifier); 473 if (encryptionKeyLength == null) 474 { 475 encryptionKeyLength = PKCS5AlgorithmIdentifier.getCipherKeySizeBits( 476 cipherIdentifier); 477 } 478 479 if ((cipherAlgorithm == null) || (cipherTransformation == null) || 480 (encryptionKeyLength == null)) 481 { 482 throw new CertException( 483 ERR_PKCS8_ENC_HANDLER_UNSUPPORTED_CIPHER.get( 484 cipherOID.getOID().toString())); 485 } 486 487 initializationVector = cipherElements[1].decodeAsOctetString().getValue(); 488 } 489 catch (final CertException e) 490 { 491 Debug.debugException(e); 492 throw e; 493 } 494 catch (final Exception e) 495 { 496 Debug.debugException(e); 497 throw new CertException( 498 ERR_PKCS8_ENC_HANDLER_CANNOT_DECODE_CIPHER_SETTINGS.get( 499 StaticUtils.getExceptionMessage(e)), 500 e); 501 } 502 503 504 // Generate the secret key to use for the decryption. 505 final SecretKey secretKey; 506 try 507 { 508 final SecretKeyFactory keyFactory = 509 CryptoHelper.getSecretKeyFactory(keyFactoryAlgorithm); 510 final PBEKeySpec keySpec = new PBEKeySpec(encryptionPassword, 511 keyFactorySalt, keyFactoryIterationCount, encryptionKeyLength); 512 secretKey = new SecretKeySpec( 513 keyFactory.generateSecret(keySpec).getEncoded(), 514 cipherAlgorithm); 515 } 516 catch (final Exception e) 517 { 518 Debug.debugException(e); 519 throw new CertException( 520 ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_DEC_SECRET_KEY.get( 521 keyFactoryAlgorithm, StaticUtils.getExceptionMessage(e)), 522 e); 523 } 524 525 526 // Generate the cipher to use for the decryption. 527 final Cipher cipher; 528 try 529 { 530 cipher = CryptoHelper.getCipher(cipherTransformation); 531 cipher.init(Cipher.DECRYPT_MODE, secretKey, 532 new IvParameterSpec(initializationVector)); 533 } 534 catch (final Exception e) 535 { 536 Debug.debugException(e); 537 throw new CertException( 538 ERR_PKCS8_ENC_HANDLER_CANNOT_CREATE_DEC_CIPHER.get( 539 cipherTransformation, StaticUtils.getExceptionMessage(e)), 540 e); 541 } 542 543 544 // Decrypt the encrypted key data. 545 final byte[] decryptedKeyData; 546 try 547 { 548 decryptedKeyData = cipher.doFinal( 549 encryptedKeyElements[1].decodeAsOctetString().getValue()); 550 } 551 catch (final Exception e) 552 { 553 Debug.debugException(e); 554 throw new CertException( 555 ERR_PKCS8_ENC_HANDLER_CANNOT_DECRYPT_KEY.get( 556 cipherTransformation, StaticUtils.getExceptionMessage(e)), 557 e); 558 } 559 560 561 // Decode and return the decrypted key. 562 try 563 { 564 return new PKCS8PrivateKey(decryptedKeyData); 565 } 566 catch (final Exception e) 567 { 568 Debug.debugException(e); 569 throw new CertException( 570 ERR_PKCS8_ENC_HANDLER_CANNOT_PARSE_DECRYPTED_KEY.get( 571 cipherTransformation, StaticUtils.getExceptionMessage(e)), 572 e); 573 } 574 } 575}