001/*
002 * Copyright 2017-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2022 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) 2017-2022 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 java.io.Serializable;
041import java.security.GeneralSecurityException;
042import java.security.KeyFactory;
043import java.security.PrivateKey;
044import java.security.spec.PKCS8EncodedKeySpec;
045import java.util.ArrayList;
046import java.util.Collections;
047import java.util.List;
048
049import com.unboundid.asn1.ASN1BitString;
050import com.unboundid.asn1.ASN1Element;
051import com.unboundid.asn1.ASN1Integer;
052import com.unboundid.asn1.ASN1ObjectIdentifier;
053import com.unboundid.asn1.ASN1OctetString;
054import com.unboundid.asn1.ASN1Sequence;
055import com.unboundid.util.Base64;
056import com.unboundid.util.CryptoHelper;
057import com.unboundid.util.Debug;
058import com.unboundid.util.NotMutable;
059import com.unboundid.util.NotNull;
060import com.unboundid.util.Nullable;
061import com.unboundid.util.OID;
062import com.unboundid.util.StaticUtils;
063import com.unboundid.util.ThreadSafety;
064import com.unboundid.util.ThreadSafetyLevel;
065
066import static com.unboundid.util.ssl.cert.CertMessages.*;
067
068
069
070/**
071 * This class provides support for decoding an X.509 private key encoded in the
072 * PKCS #8 format as defined in
073 * <A HREF="https://www.ietf.org/rfc/rfc5958.txt">RFC 5958</A>.  The private key
074 * is encoded using the ASN.1 Distinguished Encoding Rules (DER), which is a
075 * subset of BER, and is supported by the code in the
076 * {@code com.unboundid.asn1} package.  The ASN.1 specification is as follows:
077 * <PRE>
078 *   OneAsymmetricKey ::= SEQUENCE {
079 *     version                   Version,
080 *     privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
081 *     privateKey                PrivateKey,
082 *     attributes            [0] Attributes OPTIONAL,
083 *     ...,
084 *     [[2: publicKey        [1] PublicKey OPTIONAL ]],
085 *     ...
086 *   }
087 *
088 *   PrivateKeyInfo ::= OneAsymmetricKey
089 *
090 *   -- PrivateKeyInfo is used by [P12]. If any items tagged as version
091 *   -- 2 are used, the version must be v2, else the version should be
092 *   -- v1. When v1, PrivateKeyInfo is the same as it was in [RFC5208].
093 *
094 *   Version ::= INTEGER { v1(0), v2(1) } (v1, ..., v2)
095 *
096 *   PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
097 *                                      { PUBLIC-KEY,
098 *                                        { PrivateKeyAlgorithms } }
099 *
100 *   PrivateKey ::= OCTET STRING
101 *                     -- Content varies based on type of key. The
102 *                     -- algorithm identifier dictates the format of
103 *                     -- the key.
104 *
105 *   PublicKey ::= BIT STRING
106 *                     -- Content varies based on type of key. The
107 *                     -- algorithm identifier dictates the format of
108 *                     -- the key.
109 *
110 *   Attributes ::= SET OF Attribute { { OneAsymmetricKeyAttributes } }
111 *
112 *   OneAsymmetricKeyAttributes ATTRIBUTE ::= {
113 *     ... -- For local profiles
114 *   }
115 * </PRE>
116 */
117@NotMutable()
118@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
119public final class PKCS8PrivateKey
120       implements Serializable
121{
122  /**
123   * The DER type for the attributes element of the private key.
124   */
125  private static final byte TYPE_ATTRIBUTES = (byte) 0xA0;
126
127
128
129  /**
130   * The DER type for the public key element of the private key.
131   */
132  private static final byte TYPE_PUBLIC_KEY = (byte) 0x81;
133
134
135
136  /**
137   * The serial version UID for this serializable class.
138   */
139  private static final long serialVersionUID = -5551171525811450486L;
140
141
142
143  // The corresponding public key, if available.
144  @Nullable private final ASN1BitString publicKey;
145
146  // The ASN.1 element with the encoded set of attributes.
147  @Nullable private final ASN1Element attributesElement;
148
149  // The ASN.1 element with the encoded private key algorithm parameters.
150  @Nullable private final ASN1Element privateKeyAlgorithmParameters;
151
152  // The encoded representation of the private key.
153  @NotNull private final ASN1OctetString encodedPrivateKey;
154
155  // The bytes that comprise the encoded representation of the PKCS #8 private
156  // key.
157  @NotNull private final byte[] pkcs8PrivateKeyBytes;
158
159  // The decoded representation of the private key, if available.
160  @Nullable private final DecodedPrivateKey decodedPrivateKey;
161
162  // The OID for the private key algorithm.
163  @NotNull private final OID privateKeyAlgorithmOID;
164
165  // The PKCS #8 private key version.
166  @NotNull private final PKCS8PrivateKeyVersion version;
167
168  // The private key algorithm name that corresponds with the private key
169  // algorithm OID, if available.
170  @Nullable private final String privateKeyAlgorithmName;
171
172
173
174  /**
175   * Creates a new PKCS #8 private key with the provided information.
176   *
177   * @param  version                        The PKCS #8 private key version.
178   *                                        This must not be {@code null}.
179   * @param  privateKeyAlgorithmOID         The OID for the private key
180   *                                        algorithm.  This must not be
181   *                                        {@code null}.
182   * @param  privateKeyAlgorithmParameters  The ASN.1 element with the encoded
183   *                                        private key algorithm parameters.
184   *                                        This may be {@code null} if there
185   *                                        are no parameters.
186   * @param  encodedPrivateKey              The encoded representation of the
187   *                                        private key.  This must not be
188   *                                        {@code null}.
189   * @param  decodedPrivateKey              The decoded representation of the
190   *                                        private key.  This may be
191   *                                        {@code null} if the decoded
192   *                                        representation is not available.
193   * @param  attributesElement              The attributes element to include in
194   *                                        the private key.  This may be
195   *                                        {@code null} if no attributes
196   *                                        element should be included.
197   * @param  publicKey                      The public key to include in the
198   *                                        private key.  This may be
199   *                                        {@code null} if no public key should
200   *                                        be included.
201   *
202   * @throws  CertException  If a problem is encountered while creating the
203   *                         private key.
204   */
205  PKCS8PrivateKey(@NotNull final PKCS8PrivateKeyVersion version,
206                  @NotNull final OID privateKeyAlgorithmOID,
207                  @Nullable final ASN1Element privateKeyAlgorithmParameters,
208                  @NotNull final ASN1OctetString encodedPrivateKey,
209                  @Nullable final DecodedPrivateKey decodedPrivateKey,
210                  @Nullable final ASN1Element attributesElement,
211                  @Nullable final ASN1BitString publicKey)
212       throws CertException
213  {
214    this.version = version;
215    this.privateKeyAlgorithmOID = privateKeyAlgorithmOID;
216    this.privateKeyAlgorithmParameters = privateKeyAlgorithmParameters;
217    this.encodedPrivateKey = encodedPrivateKey;
218    this.decodedPrivateKey = decodedPrivateKey;
219    this.attributesElement = attributesElement;
220    this.publicKey = publicKey;
221
222    final PublicKeyAlgorithmIdentifier identifier =
223         PublicKeyAlgorithmIdentifier.forOID(privateKeyAlgorithmOID);
224    if (identifier == null)
225    {
226      privateKeyAlgorithmName = null;
227    }
228    else
229    {
230      privateKeyAlgorithmName = identifier.getName();
231    }
232
233    pkcs8PrivateKeyBytes = encode().encode();
234  }
235
236
237
238  /**
239   * Decodes the contents of the provided byte array as a PKCS #8 private key.
240   *
241   * @param  privateKeyBytes  The byte array containing the encoded PKCS #8
242   *                          private key.
243   *
244   * @throws  CertException  If the contents of the provided byte array could
245   *                         not be decoded as a valid PKCS #8 private key.
246   */
247  public PKCS8PrivateKey(@NotNull final byte[] privateKeyBytes)
248         throws CertException
249  {
250    pkcs8PrivateKeyBytes = privateKeyBytes;
251
252    final ASN1Element[] privateKeyElements;
253    try
254    {
255      privateKeyElements =
256           ASN1Sequence.decodeAsSequence(privateKeyBytes).elements();
257    }
258    catch (final Exception e)
259    {
260      Debug.debugException(e);
261      throw new CertException(
262           ERR_PRIVATE_KEY_DECODE_NOT_SEQUENCE.get(
263                StaticUtils.getExceptionMessage(e)),
264           e);
265    }
266
267    if (privateKeyElements.length < 3)
268    {
269      throw new CertException(
270           ERR_PRIVATE_KEY_DECODE_NOT_ENOUGH_ELEMENTS.get(
271                privateKeyElements.length));
272    }
273
274    try
275    {
276      final int versionIntValue =
277           privateKeyElements[0].decodeAsInteger().intValue();
278      version = PKCS8PrivateKeyVersion.valueOf(versionIntValue);
279      if (version == null)
280      {
281        throw new CertException(
282             ERR_PRIVATE_KEY_DECODE_UNSUPPORTED_VERSION.get(versionIntValue));
283      }
284    }
285    catch (final CertException e)
286    {
287      Debug.debugException(e);
288      throw e;
289    }
290    catch (final Exception e)
291    {
292      Debug.debugException(e);
293      throw new CertException(
294           ERR_PRIVATE_KEY_DECODE_CANNOT_PARSE_VERSION.get(
295                StaticUtils.getExceptionMessage(e)),
296           e);
297    }
298
299    try
300    {
301      final ASN1Element[] privateKeyAlgorithmElements =
302           privateKeyElements[1].decodeAsSequence().elements();
303      privateKeyAlgorithmOID =
304           privateKeyAlgorithmElements[0].decodeAsObjectIdentifier().getOID();
305      if (privateKeyAlgorithmElements.length > 1)
306      {
307        privateKeyAlgorithmParameters = privateKeyAlgorithmElements[1];
308      }
309      else
310      {
311        privateKeyAlgorithmParameters = null;
312      }
313
314      encodedPrivateKey = privateKeyElements[2].decodeAsOctetString();
315    }
316    catch (final Exception e)
317    {
318      Debug.debugException(e);
319      throw new CertException(
320           ERR_PRIVATE_KEY_DECODE_CANNOT_PARSE_ALGORITHM.get(
321                StaticUtils.getExceptionMessage(e)),
322           e);
323    }
324
325    final PublicKeyAlgorithmIdentifier privateKeyAlgorithmIdentifier =
326         PublicKeyAlgorithmIdentifier.forOID(privateKeyAlgorithmOID);
327    if (privateKeyAlgorithmIdentifier == null)
328    {
329      privateKeyAlgorithmName = null;
330      decodedPrivateKey = null;
331    }
332    else
333    {
334      privateKeyAlgorithmName = privateKeyAlgorithmIdentifier.getName();
335
336      DecodedPrivateKey pk = null;
337      switch (privateKeyAlgorithmIdentifier)
338      {
339        case RSA:
340          try
341          {
342            pk = new RSAPrivateKey(encodedPrivateKey);
343          }
344          catch (final Exception e)
345          {
346            Debug.debugException(e);
347          }
348          break;
349
350        case EC:
351          try
352          {
353            pk = new EllipticCurvePrivateKey(encodedPrivateKey);
354          }
355          catch (final Exception e)
356          {
357            Debug.debugException(e);
358          }
359          break;
360      }
361
362      decodedPrivateKey = pk;
363    }
364
365    ASN1BitString pk = null;
366    ASN1Element attrsElement = null;
367    for (int i=3; i < privateKeyElements.length; i++)
368    {
369      final ASN1Element element = privateKeyElements[i];
370      switch (element.getType())
371      {
372        case TYPE_ATTRIBUTES:
373          attrsElement = element;
374          break;
375        case TYPE_PUBLIC_KEY:
376          try
377          {
378            pk = ASN1BitString.decodeAsBitString(element);
379          }
380          catch (final Exception e)
381          {
382            Debug.debugException(e);
383            throw new CertException(
384                 ERR_PRIVATE_KEY_DECODE_CANNOT_PARSE_PUBLIC_KEY.get(
385                      StaticUtils.getExceptionMessage(e)),
386                 e);
387          }
388          break;
389      }
390    }
391
392    attributesElement = attrsElement;
393    publicKey = pk;
394  }
395
396
397
398  /**
399   * Wraps the provided RSA private key bytes inside a full PKCS #8 encoded
400   * private key.
401   *
402   * @param  rsaPrivateKeyBytes  The bytes that comprise just the RSA private
403   *                             key.
404   *
405   * @return  The bytes that comprise a PKCS #8 encoded representation of the
406   *          provided RSA private key.
407   *
408   * @throws  CertException  If a problem is encountered while trying to wrap
409   *                         the private key.
410   */
411  @NotNull()
412  static byte[] wrapRSAPrivateKey(@NotNull final byte[] rsaPrivateKeyBytes)
413         throws CertException
414  {
415    try
416    {
417      final ArrayList<ASN1Element> elements = new ArrayList<>(5);
418      elements.add(new ASN1Integer(PKCS8PrivateKeyVersion.V1.getIntValue()));
419      elements.add(new ASN1Sequence(new ASN1ObjectIdentifier(
420           PublicKeyAlgorithmIdentifier.RSA.getOID())));
421      elements.add(new ASN1OctetString(rsaPrivateKeyBytes));
422      return new ASN1Sequence(elements).encode();
423    }
424    catch (final Exception e)
425    {
426      Debug.debugException(e);
427      throw new CertException(
428           ERR_PRIVATE_KEY_WRAP_RSA_KEY_ERROR.get(
429                StaticUtils.getExceptionMessage(e)),
430           e);
431    }
432  }
433
434
435
436  /**
437   * Encodes this PKCS #8 private key to an ASN.1 element.
438   *
439   * @return  The encoded PKCS #8 private key.
440   *
441   * @throws  CertException  If a problem is encountered while trying to encode
442   *                         the X.509 certificate.
443   */
444  @NotNull()
445  ASN1Element encode()
446       throws CertException
447  {
448    try
449    {
450      final ArrayList<ASN1Element> elements = new ArrayList<>(5);
451      elements.add(new ASN1Integer(version.getIntValue()));
452
453      if (privateKeyAlgorithmParameters == null)
454      {
455        elements.add(new ASN1Sequence(
456             new ASN1ObjectIdentifier(privateKeyAlgorithmOID)));
457      }
458      else
459      {
460        elements.add(new ASN1Sequence(
461             new ASN1ObjectIdentifier(privateKeyAlgorithmOID),
462             privateKeyAlgorithmParameters));
463      }
464
465      elements.add(encodedPrivateKey);
466
467      if (attributesElement != null)
468      {
469        elements.add(new ASN1Element(TYPE_ATTRIBUTES,
470             attributesElement.getValue()));
471      }
472
473      if (publicKey != null)
474      {
475        elements.add(new ASN1BitString(TYPE_PUBLIC_KEY, publicKey.getBits()));
476      }
477
478      return new ASN1Sequence(elements);
479    }
480    catch (final Exception e)
481    {
482      Debug.debugException(e);
483      throw new CertException(
484           ERR_PRIVATE_KEY_ENCODE_ERROR.get(toString(),
485                StaticUtils.getExceptionMessage(e)),
486           e);
487    }
488  }
489
490
491
492  /**
493   * Retrieves the bytes that comprise the encoded representation of this
494   * PKCS #8 private key.
495   *
496   * @return  The bytes that comprise the encoded representation of this PKCS #8
497   *          private key.
498   */
499  @NotNull()
500  public byte[] getPKCS8PrivateKeyBytes()
501  {
502    return pkcs8PrivateKeyBytes;
503  }
504
505
506
507  /**
508   * Retrieves the private key version.
509   *
510   * @return  The private key version.
511   */
512  @NotNull()
513  public PKCS8PrivateKeyVersion getVersion()
514  {
515    return version;
516  }
517
518
519
520  /**
521   * Retrieves the private key algorithm OID.
522   *
523   * @return  The private key algorithm OID.
524   */
525  @NotNull()
526  public OID getPrivateKeyAlgorithmOID()
527  {
528    return privateKeyAlgorithmOID;
529  }
530
531
532
533  /**
534   * Retrieves the private key algorithm name, if available.
535   *
536   * @return  The private key algorithm name, or {@code null} if private key
537   *          algorithm OID is not recognized.
538   */
539  @Nullable()
540  public String getPrivateKeyAlgorithmName()
541  {
542    return privateKeyAlgorithmName;
543  }
544
545
546
547  /**
548   * Retrieves the private key algorithm name, if available, or a string
549   * representation of the OID if the name is not available.
550   *
551   * @return  The private key algorithm name if it is available, or a string
552   *          representation of the private key algorithm OID if it is not.
553   */
554  @NotNull()
555  public String getPrivateKeyAlgorithmNameOrOID()
556  {
557    if (privateKeyAlgorithmName == null)
558    {
559      return privateKeyAlgorithmOID.toString();
560    }
561    else
562    {
563      return privateKeyAlgorithmName;
564    }
565  }
566
567
568
569  /**
570   * Retrieves the encoded private key algorithm parameters, if present.
571   *
572   * @return  The encoded private key algorithm parameters, or {@code null} if
573   *          there are no private key algorithm parameters.
574   */
575  @Nullable()
576  public ASN1Element getPrivateKeyAlgorithmParameters()
577  {
578    return privateKeyAlgorithmParameters;
579  }
580
581
582
583  /**
584   * Retrieves the encoded private key data.
585   *
586   * @return  The encoded private key data.
587   */
588  @NotNull()
589  public ASN1OctetString getEncodedPrivateKey()
590  {
591    return encodedPrivateKey;
592  }
593
594
595
596  /**
597   * Retrieves the decoded private key, if available.
598   *
599   * @return  The decoded private key, or {@code null} if the decoded key is
600   *          not available.
601   */
602  @Nullable()
603  public DecodedPrivateKey getDecodedPrivateKey()
604  {
605    return decodedPrivateKey;
606  }
607
608
609
610  /**
611   * Retrieves an ASN.1 element containing an encoded set of private key
612   * attributes, if available.
613   *
614   * @return  An ASN.1 element containing an encoded set of private key
615   *          attributes, or {@code null} if the private key does not have any
616   *          attributes.
617   */
618  @Nullable()
619  public ASN1Element getAttributesElement()
620  {
621    return attributesElement;
622  }
623
624
625
626  /**
627   * Retrieves the public key included in the private key, if available.
628   *
629   * @return  The public key included in the private key, or {@code null} if the
630   *          private key does not include a public key.
631   */
632  @Nullable()
633  public ASN1BitString getPublicKey()
634  {
635    return publicKey;
636  }
637
638
639
640  /**
641   * Converts this PKCS #8 private key object to a Java {@code PrivateKey}
642   * object.
643   *
644   * @return  The Java {@code PrivateKey} object that corresponds to this
645   *          PKCS #8 private key.
646   *
647   * @throws  GeneralSecurityException  If a problem is encountered while
648   *                                    performing the conversion.
649   */
650  @NotNull()
651  public PrivateKey toPrivateKey()
652         throws GeneralSecurityException
653  {
654    final KeyFactory keyFactory =
655         CryptoHelper.getKeyFactory(getPrivateKeyAlgorithmNameOrOID());
656    return keyFactory.generatePrivate(
657         new PKCS8EncodedKeySpec(pkcs8PrivateKeyBytes));
658  }
659
660
661
662  /**
663   * Retrieves a string representation of the decoded X.509 certificate.
664   *
665   * @return  A string representation of the decoded X.509 certificate.
666   */
667  @Override()
668  @NotNull()
669  public String toString()
670  {
671    final StringBuilder buffer = new StringBuilder();
672    toString(buffer);
673    return buffer.toString();
674  }
675
676
677
678  /**
679   * Appends a string representation of the decoded X.509 certificate to the
680   * provided buffer.
681   *
682   * @param  buffer  The buffer to which the information should be appended.
683   */
684  public void toString(@NotNull final StringBuilder buffer)
685  {
686    buffer.append("PKCS8PrivateKey(version='");
687    buffer.append(version.getName());
688    buffer.append("', privateKeyAlgorithmOID=");
689    buffer.append(privateKeyAlgorithmOID.toString());
690    buffer.append('\'');
691
692    if (privateKeyAlgorithmName != null)
693    {
694      buffer.append(", privateKeyAlgorithmName='");
695      buffer.append(privateKeyAlgorithmName);
696      buffer.append('\'');
697    }
698
699    if (decodedPrivateKey == null)
700    {
701      buffer.append(", encodedPrivateKey='");
702      StaticUtils.toHex(encodedPrivateKey.getValue(), ":", buffer);
703      buffer.append('\'');
704    }
705    else
706    {
707      buffer.append(", decodedPrivateKey=");
708      decodedPrivateKey.toString(buffer);
709
710
711      if (decodedPrivateKey instanceof EllipticCurvePrivateKey)
712      {
713        try
714        {
715          final OID namedCurveOID = privateKeyAlgorithmParameters.
716               decodeAsObjectIdentifier().getOID();
717          buffer.append(", ellipticCurvePrivateKeyParameters=namedCurve='");
718          buffer.append(NamedCurve.getNameOrOID(namedCurveOID));
719          buffer.append('\'');
720        }
721        catch (final Exception e)
722        {
723          Debug.debugException(e);
724        }
725      }
726    }
727
728    buffer.append("')");
729  }
730
731
732
733  /**
734   * Retrieves a list of the lines that comprise a PEM representation of this
735   * certificate signing request.
736   *
737   * @return  A list of the lines that comprise a PEM representation of this
738   *          certificate signing request.
739   */
740  @NotNull()
741  public List<String> toPEM()
742  {
743    final ArrayList<String> lines = new ArrayList<>(10);
744    lines.add("-----BEGIN PRIVATE KEY-----");
745
746    final String keyBase64 = Base64.encode(pkcs8PrivateKeyBytes);
747    lines.addAll(StaticUtils.wrapLine(keyBase64, 64));
748
749    lines.add("-----END PRIVATE KEY-----");
750
751    return Collections.unmodifiableList(lines);
752  }
753
754
755
756  /**
757   * Retrieves a multi-line string containing a PEM representation of this
758   * certificate signing request.
759   *
760   * @return  A multi-line string containing a PEM representation of this
761   *          certificate signing request.
762   */
763  @NotNull()
764  public String toPEMString()
765  {
766    final StringBuilder buffer = new StringBuilder();
767    buffer.append("-----BEGIN PRIVATE KEY-----");
768    buffer.append(StaticUtils.EOL);
769
770    final String keyBase64 = Base64.encode(pkcs8PrivateKeyBytes);
771    for (final String line : StaticUtils.wrapLine(keyBase64, 64))
772    {
773      buffer.append(line);
774      buffer.append(StaticUtils.EOL);
775    }
776    buffer.append("-----END PRIVATE KEY-----");
777    buffer.append(StaticUtils.EOL);
778
779    return buffer.toString();
780  }
781}