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}