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}