001/* 002 * Copyright 2021-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2021-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) 2021-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.ldap.sdk.unboundidds.extensions; 037 038 039 040import java.io.BufferedInputStream; 041import java.io.File; 042import java.io.FileInputStream; 043import java.io.IOException; 044import java.io.InputStream; 045import java.util.ArrayList; 046import java.util.Arrays; 047import java.util.Collections; 048import java.util.List; 049 050import com.unboundid.asn1.ASN1Element; 051import com.unboundid.asn1.ASN1OctetString; 052import com.unboundid.asn1.ASN1Sequence; 053import com.unboundid.asn1.ASN1StreamReader; 054import com.unboundid.ldap.sdk.LDAPException; 055import com.unboundid.ldap.sdk.ResultCode; 056import com.unboundid.util.Debug; 057import com.unboundid.util.NotMutable; 058import com.unboundid.util.NotNull; 059import com.unboundid.util.Nullable; 060import com.unboundid.util.PasswordFileReader; 061import com.unboundid.util.StaticUtils; 062import com.unboundid.util.ThreadSafety; 063import com.unboundid.util.ThreadSafetyLevel; 064import com.unboundid.util.Validator; 065import com.unboundid.util.ssl.cert.CertException; 066import com.unboundid.util.ssl.cert.PKCS8EncryptionHandler; 067import com.unboundid.util.ssl.cert.PKCS8PEMFileReader; 068import com.unboundid.util.ssl.cert.PKCS8PrivateKey; 069import com.unboundid.util.ssl.cert.X509Certificate; 070import com.unboundid.util.ssl.cert.X509PEMFileReader; 071 072import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 073 074 075 076/** 077 * This class provides a {@link ReplaceCertificateKeyStoreContent} 078 * implementation to indicate that the certificate chain and private key (in 079 * either PEM or DER format) are provided directly in the extended request. 080 * <BR> 081 * <BLOCKQUOTE> 082 * <B>NOTE:</B> This class, and other classes within the 083 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 084 * supported for use against Ping Identity, UnboundID, and 085 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 086 * for proprietary functionality or for external specifications that are not 087 * considered stable or mature enough to be guaranteed to work in an 088 * interoperable way with other types of LDAP servers. 089 * </BLOCKQUOTE> 090 */ 091@NotMutable() 092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 093public final class CertificateDataReplaceCertificateKeyStoreContent 094 extends ReplaceCertificateKeyStoreContent 095{ 096 /** 097 * The BER type to use for the ASN.1 element containing an encoded 098 * representation of this key store content object. 099 */ 100 static final byte TYPE_KEY_STORE_CONTENT = (byte) 0xA2; 101 102 103 104 /** 105 * The BER type to use for the ASN.1 element that provides the new 106 * certificate chain. 107 */ 108 private static final byte TYPE_CERTIFICATE_CHAIN = (byte) 0xAE; 109 110 111 112 /** 113 * The BER type to use for the ASN.1 element that provides the private key for 114 * the new certificate. 115 */ 116 private static final byte TYPE_PRIVATE_KEY = (byte) 0xAF; 117 118 119 120 /** 121 * The serial version UID for this serializable class. 122 */ 123 private static final long serialVersionUID = 1771837307666073616L; 124 125 126 127 // An encoded representation of the PKCS #8 private key. 128 @Nullable private final byte[] privateKeyData; 129 130 // An encoded representation of the X.509 certificates in the certificate 131 // chain. 132 @NotNull private final List<byte[]> certificateChainData; 133 134 135 136 /** 137 * Creates a new instance of this key store content object with the provided 138 * information. 139 * 140 * @param certificateChainData A list containing the encoded representations 141 * of the X.509 certificates in the new 142 * certificate chain. Each byte array must 143 * contain the PEM or DER representation of a 144 * single certificate in the chain, with the 145 * first certificate being the end-entity 146 * certificate, and each subsequent certificate 147 * being the issuer for the previous 148 * certificate. This must not be {@code null} 149 * or empty. 150 * @param privateKeyData An array containing the encoded 151 * representation of the PKCS #8 private key 152 * for the end-entity certificate in the chain. 153 * It may be encoded in either PEM or DER 154 * format. This may be {@code null} if the 155 * new end-entity certificate uses the same 156 * private key as the certificate currently in 157 * use in the server. 158 */ 159 public CertificateDataReplaceCertificateKeyStoreContent( 160 @NotNull final List<byte[]> certificateChainData, 161 @Nullable final byte[] privateKeyData) 162 { 163 Validator.ensureNotNullOrEmpty(certificateChainData, 164 "CertificateDataReplaceCertificateKeyStoreContent." + 165 "certificateChainData must not be null or empty."); 166 167 this.certificateChainData = Collections.unmodifiableList( 168 new ArrayList<>(certificateChainData)); 169 this.privateKeyData = privateKeyData; 170 } 171 172 173 174 /** 175 * Creates a new instance of this key store content object with the provided 176 * information. 177 * 178 * @param certificateChainFiles A list containing one or more files from 179 * which to read the PEM or DER representations 180 * of the X.509 certificates to include in 181 * the new certificate chain. The order of 182 * the files, and the order of the certificates 183 * in each file, should be arranged such that 184 * the first certificate read is the end-entity 185 * certificate and each subsequent certificate 186 * is the issuer for the previous. This must 187 * not be {@code null} or empty. 188 * @param privateKeyFile A file from which to read the PEM or DER 189 * representation of the PKCS #8 private key 190 * for the end-entity certificate in the chain. 191 * This may be {@code null} if the new 192 * end-entity certificate uses the same private 193 * key as the certificate currently in use in 194 * the server. The private key must not be 195 * encrypted. 196 * 197 * @throws LDAPException If a problem occurs while trying to read or parse 198 * data contained in any of the provided files. 199 */ 200 public CertificateDataReplaceCertificateKeyStoreContent( 201 @NotNull final List<File> certificateChainFiles, 202 @Nullable final File privateKeyFile) 203 throws LDAPException 204 { 205 this(readCertificateChain(certificateChainFiles), 206 ((privateKeyFile == null) ? null : readPrivateKey(privateKeyFile))); 207 } 208 209 210 211 /** 212 * Creates a new instance of this key store content object with the provided 213 * information. 214 * 215 * @param certificateChainFiles 216 * A list containing one or more files from which to read the PEM 217 * or DER representations of the X.509 certificates to include in 218 * the new certificate chain. The order of the files, and the 219 * order of the certificates in each file, should be arranged 220 * such that the first certificate read is the end-entity 221 * certificate and each subsequent certificate is the issuer for 222 * the previous. This must not be {@code null} or empty. 223 * @param privateKeyFile 224 * A file from which to read the PEM or DER representation of the 225 * PKCS #8 private key for the end-entity certificate in the 226 * chain. This may be {@code null} if the new end-entity 227 * certificate uses the same private key as the certificate 228 * currently in use in the server. 229 * @param privateKeyEncryptionPasswordFile 230 * A file that contains the password needed to decrypt the 231 * private key if it is encrypted. This may be {@code null} if 232 * the private key is not encrypted. 233 * 234 * @throws LDAPException If a problem occurs while trying to read or parse 235 * data contained in any of the provided files. 236 */ 237 public CertificateDataReplaceCertificateKeyStoreContent( 238 @NotNull final List<File> certificateChainFiles, 239 @Nullable final File privateKeyFile, 240 @Nullable final File privateKeyEncryptionPasswordFile) 241 throws LDAPException 242 { 243 this(readCertificateChain(certificateChainFiles), 244 ((privateKeyFile == null) ? null : 245 readPrivateKey(privateKeyFile, 246 privateKeyEncryptionPasswordFile))); 247 } 248 249 250 251 /** 252 * Reads a certificate chain from the given file or set of files. Each file 253 * must contain the PEM or DER representations of one or more X.509 254 * certificates. If a file contains multiple certificates, all certificates 255 * in that file must be either all PEM-formatted or all DER-formatted. 256 * 257 * @param files The set of files from which the certificate chain should be 258 * read. It must not be {@code null} or empty. 259 * 260 * @return A list containing the encoded representation of the X.509 261 * certificates read from the file, with each byte array containing 262 * the encoded representation for one certificate. 263 * 264 * @throws LDAPException If a problem was encountered while attempting to 265 * read from or parse the content of any of the files. 266 */ 267 @NotNull() 268 public static List<byte[]> readCertificateChain(@NotNull final File... files) 269 throws LDAPException 270 { 271 return readCertificateChain(Arrays.asList(files)); 272 } 273 274 275 276 /** 277 * Reads a certificate chain from the given file or set of files. Each file 278 * must contain the PEM or DER representations of one or more X.509 279 * certificates. If a file contains multiple certificates, all certificates 280 * in that file must be either all PEM-formatted or all DER-formatted. 281 * 282 * @param files The set of files from which the certificate chain should be 283 * read. It must not be {@code null} or empty. 284 * 285 * @return A list containing the encoded representation of the X.509 286 * certificates read from the file, with each byte array containing 287 * the encoded representation for one certificate. 288 * 289 * @throws LDAPException If a problem was encountered while attempting to 290 * read from or parse the content of any of the files. 291 */ 292 @NotNull() 293 public static List<byte[]> readCertificateChain( 294 @NotNull final List<File> files) 295 throws LDAPException 296 { 297 Validator.ensureNotNullOrEmpty(files, 298 "CertificateDataReplaceCertificateKeyStoreContent." + 299 "readCertificateChain.files must not be null or empty."); 300 301 final List<byte[]> encodedCerts = new ArrayList<>(); 302 for (final File f : files) 303 { 304 readCertificates(f, encodedCerts); 305 } 306 307 return Collections.unmodifiableList(encodedCerts); 308 } 309 310 311 312 /** 313 * Reads one or more certificates from the specified file. The certificates 314 * may be in either PEM format or DER format, but if there are multiple 315 * certificates in the file, they must all be in the same format. 316 * 317 * @param file The file to be read. It must not be {@code null}. 318 * @param encodedCerts A list that will be updated with the certificates 319 * that are read. This must not be {@code null} and 320 * must be updatable. 321 * 322 * @throws LDAPException If a problem was encountered while attempting to 323 * read from or parse the content of the specified 324 * file. 325 */ 326 private static void readCertificates(@NotNull final File file, 327 @NotNull final List<byte[]> encodedCerts) 328 throws LDAPException 329 { 330 // Open the file for reading. 331 try (FileInputStream fis = new FileInputStream(file); 332 BufferedInputStream bis = new BufferedInputStream(fis)) 333 { 334 // Peek at the first byte of the file. 335 bis.mark(1); 336 final int firstByte = bis.read(); 337 bis.reset(); 338 339 340 // If the file is empty, then throw an exception. 341 if (firstByte < 0x00) 342 { 343 throw new LDAPException(ResultCode.PARAM_ERROR, 344 ERR_CD_KSC_DECODE_ERR_EMPTY_CERT_FILE.get(file.getAbsolutePath())); 345 } 346 347 348 // If the first byte is 0x30, then that indicates that it's the first byte 349 // of a DER sequence. Assume all the certificates in the file are in the 350 // DER format. 351 if (firstByte == 0x30) 352 { 353 readDERCertificates(file, bis, encodedCerts); 354 return; 355 } 356 357 358 // If the file is PEM-formatted, then the first byte will probably be 359 // 0x2D (which is the ASCII '-' character, which will appear at the start 360 // of the "-----BEGIN CERTIFICATE-----" header). However, we also support 361 // blank lines and comment lines starting with '#', so we'll just fall 362 // back to assuing that it's PEM. 363 readPEMCertificates(file, bis, encodedCerts); 364 } 365 catch (final IOException e) 366 { 367 Debug.debugException(e); 368 throw new LDAPException(ResultCode.LOCAL_ERROR, 369 ERR_CD_KSC_DECODE_ERROR_READING_CERT_FILE.get(file.getAbsolutePath(), 370 StaticUtils.getExceptionMessage(e)), 371 e); 372 } 373 } 374 375 376 377 /** 378 * Reads one or more DER-formatted X.509 certificates from the given input 379 * stream. 380 * 381 * @param file The file with which the provided input stream is 382 * associated. It must not be {@code null}. 383 * @param inputStream The input stream from which the certificates are to 384 * be read. It must not be {@code null}. 385 * @param encodedCerts A list that will be updated with the certificates 386 * that are read. This must not be {@code null} and 387 * must be updatable. 388 * 389 * @throws LDAPException If a problem occurs while trying to read from the 390 * file or parse the data as ASN.1 DER elements. 391 */ 392 private static void readDERCertificates( 393 @NotNull final File file, 394 @NotNull final InputStream inputStream, 395 @NotNull final List<byte[]> encodedCerts) 396 throws LDAPException 397 { 398 try (ASN1StreamReader asn1Reader = new ASN1StreamReader(inputStream)) 399 { 400 while (true) 401 { 402 final ASN1Element element = asn1Reader.readElement(); 403 if (element == null) 404 { 405 return; 406 } 407 408 encodedCerts.add(element.encode()); 409 } 410 } 411 catch (final IOException e) 412 { 413 Debug.debugException(e); 414 415 // Even though it's possible that it's an I/O problem, it's actually much 416 // more likely to be a decoding problem. 417 throw new LDAPException(ResultCode.DECODING_ERROR, 418 ERR_CD_KSC_DECODE_DER_CERT_ERROR.get(file.getAbsolutePath(), 419 StaticUtils.getExceptionMessage(e)), 420 e); 421 } 422 } 423 424 425 426 /** 427 * Reads one or more PEM-formatted X.509 certificates from the given input 428 * stream. 429 * 430 * @param file The file with which the provided input stream is 431 * associated. It must not be {@code null}. 432 * @param inputStream The input stream from which the certificates are to 433 * be read. It must not be {@code null}. 434 * @param encodedCerts A list that will be updated with the certificates 435 * that are read. This must not be {@code null} and 436 * must be updatable. 437 * 438 * @throws IOException If a problem occurs while trying to read from the 439 * file. 440 * 441 * @throws LDAPException If the contents of the file cannot be parsed as a 442 * valid set of PEM-formatted certificates. 443 */ 444 private static void readPEMCertificates( 445 @NotNull final File file, 446 @NotNull final InputStream inputStream, 447 @NotNull final List<byte[]> encodedCerts) 448 throws IOException, LDAPException 449 { 450 try (X509PEMFileReader pemReader = new X509PEMFileReader(inputStream)) 451 { 452 while (true) 453 { 454 final X509Certificate cert = pemReader.readCertificate(); 455 if (cert == null) 456 { 457 return; 458 } 459 460 encodedCerts.add(cert.getX509CertificateBytes()); 461 } 462 } 463 catch (final CertException e) 464 { 465 Debug.debugException(e); 466 throw new LDAPException(ResultCode.DECODING_ERROR, 467 ERR_CD_KSC_DECODE_PEM_CERT_ERROR.get(file.getAbsolutePath(), 468 e.getMessage()), 469 e); 470 } 471 } 472 473 474 475 /** 476 * Reads a PKCS #8 private key from the given file. The file must contain the 477 * PEM or DER representation of a single private key. 478 * 479 * @param file The file from which the private key should be read. It must 480 * not be {@code null}. 481 * 482 * @return The encoded representation of the PKCS #8 private key that was 483 * read. 484 * 485 * @throws LDAPException If a problem occurs while trying to read from 486 * or parse the content of the specified file. 487 */ 488 @NotNull() 489 public static byte[] readPrivateKey(@NotNull final File file) 490 throws LDAPException 491 { 492 return readPrivateKey(file, null); 493 } 494 495 496 497 /** 498 * Reads a PKCS #8 private key from the given file. The file must contain the 499 * PEM or DER representation of a single private key. 500 * 501 * @param file The file from which the private key should 502 * be read. It must not be {@code null}. 503 * @param encryptionPasswordFile The file containing the password needed to 504 * decrypt the private key if it is encrypted. 505 * It may be {@code null} if the private key 506 * is not encrypted. 507 * 508 * @return The encoded representation of the PKCS #8 private key that was 509 * read. 510 * 511 * @throws LDAPException If a problem occurs while trying to read from 512 * or parse the content of the specified file. 513 */ 514 @NotNull() 515 public static byte[] readPrivateKey(@NotNull final File file, 516 @Nullable final File encryptionPasswordFile) 517 throws LDAPException 518 { 519 Validator.ensureNotNull(file, 520 "CertificateDataReplaceCertificateKeyStoreContent." + 521 "readPrivateKey.file must not be null."); 522 523 524 // If there is an encryption password file, then read the password. 525 final char[] encryptionPassword; 526 if (encryptionPasswordFile == null) 527 { 528 encryptionPassword = null; 529 } 530 else 531 { 532 final PasswordFileReader passwordFileReader = new PasswordFileReader(); 533 try 534 { 535 encryptionPassword = 536 passwordFileReader.readPassword(encryptionPasswordFile); 537 } 538 catch (final LDAPException e) 539 { 540 Debug.debugException(e); 541 throw e; 542 } 543 catch (final Exception e) 544 { 545 Debug.debugException(e); 546 throw new LDAPException(ResultCode.LOCAL_ERROR, 547 ERR_CD_KSC_ERROR_READING_PW_FILE.get( 548 encryptionPasswordFile.getAbsolutePath(), 549 StaticUtils.getExceptionMessage(e)), 550 e); 551 } 552 } 553 554 555 // Open the file for reading. 556 try (FileInputStream fis = new FileInputStream(file); 557 BufferedInputStream bis = new BufferedInputStream(fis)) 558 { 559 // Read the first byte of the file. 560 bis.mark(1); 561 final int firstByte = bis.read(); 562 bis.reset(); 563 564 565 // If the file is empty, then throw an exception, as that's not allowed. 566 if (firstByte < 0) 567 { 568 throw new LDAPException(ResultCode.PARAM_ERROR, 569 ERR_CD_KSC_DECODE_ERROR_EMPTY_PK_FILE.get(file.getAbsolutePath())); 570 } 571 572 573 // If the first byte is 0x30, then that indicates it's a DER sequence. 574 if (firstByte == 0x30) 575 { 576 return readDERPrivateKey(file, bis, encryptionPassword); 577 } 578 579 580 // Assume that the file is PEM-formatted. 581 return readPEMPrivateKey(file, bis, encryptionPassword); 582 } 583 catch (final IOException e) 584 { 585 Debug.debugException(e); 586 throw new LDAPException(ResultCode.DECODING_ERROR, 587 ERR_CD_KSC_DECODE_ERROR_READING_PK_FILE.get(file.getAbsolutePath(), 588 StaticUtils.getExceptionMessage(e)), 589 e); 590 } 591 finally 592 { 593 if (encryptionPassword != null) 594 { 595 Arrays.fill(encryptionPassword, '\u0000'); 596 } 597 } 598 } 599 600 601 602 /** 603 * Reads a DER-formatted PKCS #8 private key from the provided input stream. 604 * 605 * @param file The file with which the provided input stream 606 * is associated. It must not be {@code null}. 607 * @param inputStream The input stream from which the private key 608 * will be read. It must not be {@code null}. 609 * @param encryptionPassword The password to use to decrypt the encrypted 610 * private key. It may be {@code null} if the 611 * private key is not encrypted. 612 * 613 * @return The bytes that comprise the encoded PKCS #8 private key. 614 * 615 * @throws LDAPException If a problem occurs while attempting to read the 616 * private key data from the given file. 617 */ 618 @NotNull() 619 private static byte[] readDERPrivateKey( 620 @NotNull final File file, 621 @NotNull final InputStream inputStream, 622 @Nullable final char[] encryptionPassword) 623 throws LDAPException 624 { 625 try (ASN1StreamReader asn1Reader = new ASN1StreamReader(inputStream)) 626 { 627 final ASN1Element element = asn1Reader.readElement(); 628 if (asn1Reader.readElement() != null) 629 { 630 throw new LDAPException(ResultCode.DECODING_ERROR, 631 ERR_CD_KSC_DECODE_MULTIPLE_DER_KEYS_IN_FILE.get( 632 file.getAbsolutePath())); 633 } 634 635 final byte[] elementsBytes = element.encode(); 636 if (encryptionPassword == null) 637 { 638 return elementsBytes; 639 } 640 else 641 { 642 final PKCS8PrivateKey privateKey = 643 PKCS8EncryptionHandler.decryptPrivateKey(elementsBytes, 644 encryptionPassword); 645 return privateKey.getPKCS8PrivateKeyBytes(); 646 } 647 } 648 catch (final Exception e) 649 { 650 Debug.debugException(e); 651 652 // Even though it's possible that it's an I/O problem, it's actually much 653 // more likely to be a decoding problem. 654 throw new LDAPException(ResultCode.DECODING_ERROR, 655 ERR_CD_KSC_DECODE_DER_PK_ERROR.get(file.getAbsolutePath(), 656 StaticUtils.getExceptionMessage(e)), 657 e); 658 } 659 } 660 661 662 663 /** 664 * Reads a PEM-formatted PKCS #8 private key from the provided input stream. 665 * 666 * @param file The file with which the provided input stream 667 * is associated. It must not be {@code null}. 668 * @param inputStream The input stream from which the private key 669 * will be read. It must not be {@code null}. 670 * @param encryptionPassword The password needed to decrypt the private key 671 * if it is encrypted. It may be {@code null} if 672 * the private key is not encrypted. 673 * 674 * @return The bytes that comprise the encoded PKCS #8 private key. 675 * 676 * @throws IOException If a problem occurs while trying to read from the 677 * file. 678 * 679 * @throws LDAPException If the contents of the file cannot be parsed as a 680 * valid PEM-formatted PKCS #8 private key. 681 */ 682 @NotNull() 683 private static byte[] readPEMPrivateKey( 684 @NotNull final File file, 685 @NotNull final InputStream inputStream, 686 @Nullable final char[] encryptionPassword) 687 throws IOException, LDAPException 688 { 689 try (PKCS8PEMFileReader pemReader = new PKCS8PEMFileReader(inputStream)) 690 { 691 final PKCS8PrivateKey privateKey = pemReader.readPrivateKey(); 692 if (pemReader.readPrivateKey(encryptionPassword) != null) 693 { 694 throw new LDAPException(ResultCode.DECODING_ERROR, 695 ERR_CD_KSC_DECODE_MULTIPLE_PEM_KEYS_IN_FILE.get( 696 file.getAbsolutePath())); 697 } 698 699 return privateKey.getPKCS8PrivateKeyBytes(); 700 } 701 catch (final CertException e) 702 { 703 Debug.debugException(e); 704 throw new LDAPException(ResultCode.DECODING_ERROR, 705 ERR_CD_KSC_DECODE_PEM_PK_ERROR.get(file.getAbsolutePath(), 706 e.getMessage()), 707 e); 708 } 709 } 710 711 712 713 /** 714 * Retrieves a list of the DER-formatted or PEM-formatted representations of 715 * the X.509 certificates in the new certificate chain. 716 * 717 * @return A list of the encoded representations of the X.509 certificates 718 * in the new certificate chain. 719 */ 720 @NotNull() 721 public List<byte[]> getCertificateChainData() 722 { 723 return certificateChainData; 724 } 725 726 727 728 /** 729 * Retrieves the DER-formatted or PEM-formatted PKCS #8 private key for the 730 * new certificate, if available. 731 * 732 * @return The encoded representation of the PKCS #8 private key for the new 733 * certificate, or {@code null} if the new certificate should use the 734 * same private key as the current certificate. 735 */ 736 @Nullable() 737 public byte[] getPrivateKeyData() 738 { 739 return privateKeyData; 740 } 741 742 743 744 /** 745 * Decodes a key store file replace certificate key store content object from 746 * the provided ASN.1 element. 747 * 748 * @param element The ASN.1 element containing the encoded representation of 749 * the key store file replace certificate key store content 750 * object. It must not be {@code null}. 751 * 752 * @return The decoded key store content object. 753 * 754 * @throws LDAPException If the provided ASN.1 element cannot be decoded as 755 * a key store file replace certificate key store 756 * content object. 757 */ 758 @NotNull() 759 static CertificateDataReplaceCertificateKeyStoreContent decodeInternal( 760 @NotNull final ASN1Element element) 761 throws LDAPException 762 { 763 try 764 { 765 final ASN1Element[] elements = element.decodeAsSequence().elements(); 766 767 final ASN1Element[] chainElements = 768 elements[0].decodeAsSequence().elements(); 769 final List<byte[]> chainBytes = new ArrayList<>(); 770 for (final ASN1Element e : chainElements) 771 { 772 chainBytes.add(e.decodeAsOctetString().getValue()); 773 } 774 775 byte[] pkBytes = null; 776 for (int i=1; i < elements.length; i++) 777 { 778 if (elements[i].getType() == TYPE_PRIVATE_KEY) 779 { 780 pkBytes = elements[i].decodeAsOctetString().getValue(); 781 } 782 } 783 784 return new CertificateDataReplaceCertificateKeyStoreContent( 785 chainBytes, pkBytes); 786 } 787 catch (final Exception e) 788 { 789 Debug.debugException(e); 790 throw new LDAPException(ResultCode.DECODING_ERROR, 791 ERR_CD_KSC_DECODE_ERROR.get(StaticUtils.getExceptionMessage(e)), 792 e); 793 } 794 } 795 796 797 798 /** 799 * {@inheritDoc} 800 */ 801 @Override() 802 @NotNull() 803 public ASN1Element encode() 804 { 805 final List<ASN1Element> elements = new ArrayList<>(2); 806 807 final List<ASN1Element> chainElements = 808 new ArrayList<>(certificateChainData.size()); 809 for (final byte[] certBytes : certificateChainData) 810 { 811 chainElements.add(new ASN1OctetString(certBytes)); 812 } 813 elements.add(new ASN1Sequence(TYPE_CERTIFICATE_CHAIN, chainElements)); 814 815 if (privateKeyData != null) 816 { 817 elements.add(new ASN1OctetString(TYPE_PRIVATE_KEY, privateKeyData)); 818 } 819 820 return new ASN1Sequence(TYPE_KEY_STORE_CONTENT, elements); 821 } 822 823 824 825 /** 826 * {@inheritDoc} 827 */ 828 @Override() 829 public void toString(@NotNull final StringBuilder buffer) 830 { 831 buffer.append("CertificateDataReplaceCertificateKeyStoreContent(" + 832 "certificateChainLength="); 833 buffer.append(certificateChainData.size()); 834 buffer.append(", privateProvided="); 835 buffer.append(privateKeyData != null); 836 buffer.append(')'); 837 } 838}