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