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.util.ArrayList;
041import java.util.List;
042
043import com.unboundid.asn1.ASN1Boolean;
044import com.unboundid.asn1.ASN1Element;
045import com.unboundid.asn1.ASN1OctetString;
046import com.unboundid.asn1.ASN1Sequence;
047import com.unboundid.ldap.sdk.Control;
048import com.unboundid.ldap.sdk.ExtendedRequest;
049import com.unboundid.ldap.sdk.ExtendedResult;
050import com.unboundid.ldap.sdk.LDAPConnection;
051import com.unboundid.ldap.sdk.LDAPException;
052import com.unboundid.ldap.sdk.ResultCode;
053import com.unboundid.util.Debug;
054import com.unboundid.util.NotMutable;
055import com.unboundid.util.NotNull;
056import com.unboundid.util.Nullable;
057import com.unboundid.util.StaticUtils;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060import com.unboundid.util.Validator;
061
062import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
063
064
065
066/**
067 * This class defines an extended request that may be used to request that a
068 * Ping Identity Directory Server instance (or related Ping Identity server
069 * product) replace its listener certificate.  The new certificate data may be
070 * contained in a key store file on the server filesystem or included in the
071 * extended request itself.
072 * <BR>
073 * <BLOCKQUOTE>
074 *   <B>NOTE:</B>  This class, and other classes within the
075 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
076 *   supported for use against Ping Identity, UnboundID, and
077 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
078 *   for proprietary functionality or for external specifications that are not
079 *   considered stable or mature enough to be guaranteed to work in an
080 *   interoperable way with other types of LDAP servers.
081 * </BLOCKQUOTE>
082 * <BR>
083 * This extended request has an OID of 1.3.6.1.4.1.30221.2.6.68 and a value with
084 * the following encoding:
085 * <PRE>
086 *   ReplaceListenerCertificateValue ::= SEQUENCE {
087 *     keyStoreContent                         CHOICE {
088 *       keyStoreFile                            [0]  KeyStoreFileSequence,
089 *       keyStoreData                            [1]  KeyStoreDataSequence,
090 *       certificateData                         [2]  CertificateDataSequence,
091 *       ... },
092 *    keyManagerProvider                       [3]  OCTET STRING,
093 *    trustBehavior                            CHOICE {
094 *      trustManagerProvider                     [4] OCTET STRING,
095 *      useJVMDefaultTrustManagerProvider        [5] NULL,
096 *      ... },
097 *    targetCertificateAlias                   [6]  OCTET STRING OPTIONAL,
098 *    reloadHTTPConnectionHandlerCertificates  [7]  BOOLEAN DEFAULT FALSE,
099 *    skipCertificateValidation                [16] BOOLEAN DEFAULT FALSE,
100 *    ... }
101 *
102 *   KeyStoreFileSequence ::= SEQUENCE {
103 *     path                    [8]  OCTET STRING,
104 *     keyStorePIN             [9]  OCTET STRING,
105 *     privateKeyPIN           [10] OCTET STRING OPTIONAL,
106 *     keyStoreType            [11] OCTET STRING OPTIONAL,
107 *     sourceCertificateAlias  [12] OCTET STRING OPTIONAL,
108 *     ... }
109 *
110 *   KeyStoreDataSequence ::= SEQUENCE {
111 *     keyStoreData            [13] OCTET STRING,
112 *     keyStorePIN             [9]  OCTET STRING,
113 *     privateKeyPIN           [10]  OCTET STRING OPTIONAL,
114 *     keyStoreType            [11] OCTET STRING OPTIONAL,
115 *     sourceCertificateAlias  [12] OCTET STRING OPTIONAL,
116 *     ... }
117 *
118 *   CertificateDataSequence ::= SEQUENCE {
119 *     certificateChain  [14] SEQUENCE SIZE (1..MAX) OF OCTET STRING,
120 *     privateKey        [15] OCTET STRING OPTIONAL,
121 *     ... }
122 * </PRE>
123 * <BR><BR>
124 *
125 * @see  ReplaceListenerCertificateExtendedResult
126 */
127@NotMutable()
128@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
129public final class ReplaceListenerCertificateExtendedRequest
130       extends ExtendedRequest
131{
132  /**
133   * The OID (1.3.6.1.4.1.30221.2.6.68) for the replace listener certificate
134   * extended request.
135   */
136  @NotNull public static final String REPLACE_LISTENER_CERT_REQUEST_OID =
137       "1.3.6.1.4.1.30221.2.6.68";
138
139
140
141  /**
142   * The BER type for the request value element that holds the name of the
143   * file-based key manager provider in which the listener certificate should be
144   * updated.
145   */
146  private static final byte TYPE_KEY_MANAGER_PROVIDER = (byte) 0x83;
147
148
149
150  /**
151   * The BER type for the request value element that holds the alias to use for
152   * the new certificate chain in the key manager.
153   */
154  private static final byte TYPE_TARGET_CERT_ALIAS = (byte) 0x86;
155
156
157
158  /**
159   * The BER type for the request value element that indicates whether to
160   * trigger a certificate reload in any configured HTTP connection handlers.
161   */
162  private static final byte TYPE_RELOAD_HTTP_CONNECTION_HANDLER_CERTS =
163       (byte) 0x87;
164
165
166
167  /**
168   * The BER type for the request value element that indicates whether to
169   * skip validation for the new certificate chain.
170   */
171  private static final byte TYPE_SKIP_CERT_VALIDATION = (byte) 0x90;
172
173
174
175  /**
176   * The serial version UID for this serializable class.
177   */
178  private static final long serialVersionUID = 3947876247774857671L;
179
180
181
182  // Indicates whether to trigger a certificate reload in any configured HTTP
183  // connection handlers.
184  private final boolean reloadHTTPConnectionHandlerCertificates;
185
186  // Indicates whether to skip validation for the new certificate chain.
187  private final boolean skipCertificateValidation;
188
189  // The object providing information about how the server should obtain the new
190  // listener certificate data.
191  @NotNull private final ReplaceCertificateKeyStoreContent keyStoreContent;
192
193  // The object providing information about how the server should handle
194  // updating trust information for the new listener certificate.
195  @NotNull private final ReplaceCertificateTrustBehavior trustBehavior;
196
197  // The name of the file-based key manager provider with information about the
198  // key store in which the new listener certificate should be stored.
199  @NotNull private final String keyManagerProvider;
200
201  // The name of the alias to use for the new listener certificate in the target
202  // key store.
203  @Nullable private final String targetCertificateAlias;
204
205
206
207  /**
208   * Creates a new replace listener certificate extended request with the
209   * provided information.
210   *
211   * @param  keyStoreContent
212   *              An object with information about how the server should obtain
213   *              the new listener certificate data.  It must not be
214   *              {@code null}.
215   * @param  keyManagerProvider
216   *              The name of the file-based key manager provider with
217   *              information about the key store in which the new listener
218   *              certificate should be stored.  It must not be {@code null}.
219   * @param  trustBehavior
220   *              An object with information about how the server should handle
221   *              updating trust information for the new listener certificate.
222   *              It must not be {@code null}.
223   * @param  targetCertificateAlias
224   *              The alias that should be used for the new listener certificate
225   *              in the target key store.  It may be {@code null} if the server
226   *              should use a default alias.
227   * @param  reloadHTTPConnectionHandlerCertificates
228   *              Indicates whether to trigger a certificate reload in any
229   *              configured HTTP connection handlers after updating the
230   *              listener certificate information.  While LDAP and JMX
231   *              connection handlers will automatically start using the new
232   *              listener certificate when negotiating new TLS sessions, HTTP
233   *              connection handlers will only do so if they are explicitly
234   *              told to reload certificate data.  However, there is a chance
235   *              that this could potentially cause issues with resuming TLS
236   *              sessions for HTTPS clients that were negotiated before the
237   *              listener certificate was updated.
238   * @param  skipCertificateValidation
239   *              Indicates whether to skip validation for the new certificate
240   *              chain.
241   * @param  requestControls
242   *              The set of controls to include in the extended request.  It
243   *              may be {@code null} or empty if no request controls should be
244   *              included.
245   */
246  public ReplaceListenerCertificateExtendedRequest(
247              @NotNull final ReplaceCertificateKeyStoreContent keyStoreContent,
248              @NotNull final String keyManagerProvider,
249              @NotNull final ReplaceCertificateTrustBehavior trustBehavior,
250              @Nullable final String targetCertificateAlias,
251              final boolean reloadHTTPConnectionHandlerCertificates,
252              final boolean skipCertificateValidation,
253              @Nullable final Control... requestControls)
254  {
255    super(REPLACE_LISTENER_CERT_REQUEST_OID,
256         encodeValue(keyStoreContent, keyManagerProvider, trustBehavior,
257              targetCertificateAlias, reloadHTTPConnectionHandlerCertificates,
258              skipCertificateValidation),
259         requestControls);
260
261    this.keyStoreContent = keyStoreContent;
262    this.keyManagerProvider = keyManagerProvider;
263    this.trustBehavior = trustBehavior;
264    this.targetCertificateAlias = targetCertificateAlias;
265    this.reloadHTTPConnectionHandlerCertificates =
266         reloadHTTPConnectionHandlerCertificates;
267    this.skipCertificateValidation = skipCertificateValidation;
268  }
269
270
271
272  /**
273   * Encodes the provided information into an ASN.1 octet string suitable for
274   * use as the encoded value for a replace listener certificate extended
275   * request.
276   *
277   * @param  keyStoreContent
278   *              An object with information about how the server should obtain
279   *              the new listener certificate data.  It must not be
280   *              {@code null}.
281   * @param  keyManagerProvider
282   *              The name of the file-based key manager provider with
283   *              information about the key store in which the new listener
284   *              certificate should be stored.  It must not be {@code null}.
285   * @param  trustBehavior
286   *              An object with information about how the server should handle
287   *              updating trust information for the new listener certificate.
288   *              It must not be {@code null}.
289   * @param  targetCertificateAlias
290   *              The alias that should be used for the new listener certificate
291   *              in the target key store.  It may be {@code null} if the server
292   *              should use a default alias.
293   * @param  reloadHTTPConnectionHandlerCertificates
294   *              Indicates whether to trigger a certificate reload in any
295   *              configured HTTP connection handlers after updating the
296   *              listener certificate information.  While LDAP and JMX
297   *              connection handlers will automatically start using the new
298   *              listener certificate when negotiating new TLS sessions, HTTP
299   *              connection handlers will only do so if they are explicitly
300   *              told to reload certificate data.  However, there is a chance
301   *              that this could potentially cause issues with resuming TLS
302   *              sessions for HTTPS clients that were negotiated before the
303   *              listener certificate was updated.
304   * @param  skipCertificateValidation
305   *              Indicates whether to skip validation for the new certificate
306   *              chain.
307   *
308   * @return  An ASN.1 octet string containing the encoded request value.
309   */
310  @NotNull()
311  private static ASN1OctetString encodeValue(
312               @NotNull final ReplaceCertificateKeyStoreContent keyStoreContent,
313               @NotNull final String keyManagerProvider,
314               @NotNull final ReplaceCertificateTrustBehavior trustBehavior,
315               @Nullable final String targetCertificateAlias,
316               final boolean reloadHTTPConnectionHandlerCertificates,
317               final boolean skipCertificateValidation)
318  {
319    Validator.ensureNotNullWithMessage(keyStoreContent,
320         "ReplaceListenerCertificateExtendedRequest.keyStoreContent must not " +
321              "be null.");
322    Validator.ensureNotNullOrEmpty(keyManagerProvider,
323         "ReplaceListenerCertificateExtendedRequest.keyManagerProvider must " +
324              "not be null or empty.");
325    Validator.ensureNotNullWithMessage(trustBehavior,
326         "ReplaceListenerCertificateExtendedRequest.trustBehavior must not " +
327              "be null.");
328
329    final List<ASN1Element> valueElements = new ArrayList<>(6);
330    valueElements.add(keyStoreContent.encode());
331    valueElements.add(new ASN1OctetString(TYPE_KEY_MANAGER_PROVIDER,
332         keyManagerProvider));
333    valueElements.add(trustBehavior.encode());
334
335    if (targetCertificateAlias != null)
336    {
337      valueElements.add(new ASN1OctetString(TYPE_TARGET_CERT_ALIAS,
338           targetCertificateAlias));
339    }
340
341    if (reloadHTTPConnectionHandlerCertificates)
342    {
343      valueElements.add(new ASN1Boolean(
344           TYPE_RELOAD_HTTP_CONNECTION_HANDLER_CERTS, true));
345    }
346
347    if (skipCertificateValidation)
348    {
349      valueElements.add(new ASN1Boolean(TYPE_SKIP_CERT_VALIDATION, true));
350    }
351
352    return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
353  }
354
355
356
357  /**
358   * Creates a new replace listener certificate extended request that is decoded
359   * from the provided generic extended request.
360   *
361   * @param  request  The generic extended request to be decoded as a replace
362   *                  listener certificate extended request.  It must not be
363   *                  {@code null}.
364   *
365   * @throws  LDAPException  If a problem occurs while attempting to decode the
366   *                         provided extended request as a replace listener
367   *                         certificate request.
368   */
369  public ReplaceListenerCertificateExtendedRequest(
370              @NotNull final ExtendedRequest request)
371         throws LDAPException
372  {
373    super(request);
374
375    final ASN1OctetString value = request.getValue();
376    if (value == null)
377    {
378      throw new LDAPException(ResultCode.DECODING_ERROR,
379           ERR_REPLACE_LISTENER_CERT_REQ_NO_VALUE.get());
380    }
381
382    try
383    {
384      final ASN1Element[] elements =
385           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
386      keyStoreContent = ReplaceCertificateKeyStoreContent.decode(elements[0]);
387      keyManagerProvider =
388           ASN1OctetString.decodeAsOctetString(elements[1]).stringValue();
389      trustBehavior = ReplaceCertificateTrustBehavior.decode(elements[2]);
390
391      String targetAlias = null;
392      boolean reloadHTTPCerts = false;
393      boolean skipValidation = false;
394      for (int i=3; i < elements.length; i++)
395      {
396        switch (elements[i].getType())
397        {
398          case TYPE_TARGET_CERT_ALIAS:
399            targetAlias = elements[i].decodeAsOctetString().stringValue();
400            break;
401          case TYPE_RELOAD_HTTP_CONNECTION_HANDLER_CERTS:
402            reloadHTTPCerts = elements[i].decodeAsBoolean().booleanValue();
403            break;
404          case TYPE_SKIP_CERT_VALIDATION:
405            skipValidation = elements[i].decodeAsBoolean().booleanValue();
406            break;
407        }
408      }
409
410      targetCertificateAlias = targetAlias;
411      reloadHTTPConnectionHandlerCertificates = reloadHTTPCerts;
412      skipCertificateValidation = skipValidation;
413    }
414    catch (final Exception e)
415    {
416      Debug.debugException(e);
417      throw new LDAPException(ResultCode.DECODING_ERROR,
418         ERR_REPLACE_LISTENER_CERT_DECODE_ERROR.get(
419              StaticUtils.getExceptionMessage(e)),
420           e);
421    }
422  }
423
424
425
426  /**
427   * Retrieves an object with information about how the server should obtain the
428   * new listener certificate data.
429   *
430   * @return  An object with information about how the server should obtain the
431   *          new listener certificate data.
432   */
433  @NotNull()
434  public ReplaceCertificateKeyStoreContent getKeyStoreContent()
435  {
436    return keyStoreContent;
437  }
438
439
440
441  /**
442   * Retrieves the name of the file-based key manager provider with information
443   * about the key store in which thew new listener certificate should be
444   * stored.
445   *
446   * @return  The name of the file-based key manager provider with information
447   *          about the key store in which the new listener certificate should
448   *          be stored.
449   */
450  @NotNull()
451  public String getKeyManagerProvider()
452  {
453    return keyManagerProvider;
454  }
455
456
457
458  /**
459   * Retrieves an object with information about how the server should handle
460   * updating trust information for the new listener certificate.
461   *
462   * @return  An object with information about how the server should handle
463   *          updating trust information for the new listener certificate.
464   */
465  @NotNull()
466  public ReplaceCertificateTrustBehavior getTrustBehavior()
467  {
468    return trustBehavior;
469  }
470
471
472
473  /**
474   * Retrieves the alias that should be used for the new listener certificate in
475   * the target key store, if provided.
476   *
477   * @return  The alias that should be used for the new listener certificate in
478   *          the target key store, or {@code null} if the server should use a
479   *          default alias.
480   */
481  @Nullable()
482  public String getTargetCertificateAlias()
483  {
484    return targetCertificateAlias;
485  }
486
487
488
489  /**
490   * Indicates whether to trigger a certificate reload in any configured HTTP
491   * connection handlers after updating the listener certificate information.
492   * While LDAP and JMX connection handlers will automatically start using the
493   * new listener certificate when negotiating new TLS sessions, HTTP connection
494   * handlers will only do so if they are explicitly told to reload certificate
495   * data.  However, there is a chance that this could potentially cause issues
496   * with resuming TLS sessions for HTTPS clients that were negotiated before
497   * the listener certificate was updated.
498   *
499   * @return  {@code true} if the server should reload certificates in any
500   *          configured HTTP connection handlers after updating the listener
501   *          certificates information, or {@code false} if not.
502   */
503  public boolean reloadHTTPConnectionHandlerCertificates()
504  {
505    return reloadHTTPConnectionHandlerCertificates;
506  }
507
508
509
510  /**
511   * Indicates whether the server should skip validation processing for the
512   * new certificate chain.
513   *
514   * @return  {@code true} if the server should skip validation processing for
515   *          the new certificate chain, or {@code false} if not.
516   */
517  public boolean skipCertificateValidation()
518  {
519    return skipCertificateValidation;
520  }
521
522
523
524  /**
525   * {@inheritDoc}
526   */
527  @Override()
528  @NotNull()
529  public ReplaceListenerCertificateExtendedResult process(
530              @NotNull final LDAPConnection connection, final int depth)
531         throws LDAPException
532  {
533    final ExtendedResult extendedResponse = super.process(connection, depth);
534    return new ReplaceListenerCertificateExtendedResult(extendedResponse);
535  }
536
537
538
539  /**
540   * {@inheritDoc}
541   */
542  @Override()
543  @NotNull()
544  public String getExtendedRequestName()
545  {
546    return INFO_REPLACE_LISTENER_CERT_REQUEST_NAME.get();
547  }
548
549
550
551  /**
552   * {@inheritDoc}
553   */
554  @Override()
555  public void toString(@NotNull final StringBuilder buffer)
556  {
557    buffer.append("ReplaceListenerCertificateExtendedRequest(oid='");
558    buffer.append(getOID());
559    buffer.append("', keyStoreContent=");
560    keyStoreContent.toString(buffer);
561    buffer.append(", keyManagerProvider='");
562    buffer.append(keyManagerProvider);
563    buffer.append("', trustBehavior=");
564    trustBehavior.toString(buffer);
565
566    if (targetCertificateAlias != null)
567    {
568      buffer.append(", targetCertificateAlias='");
569      buffer.append(targetCertificateAlias);
570      buffer.append('\'');
571    }
572
573    buffer.append(", reloadHTTPConnectionHandlerCertificates=");
574    buffer.append(reloadHTTPConnectionHandlerCertificates);
575    buffer.append(", skipCertificateValidation=");
576    buffer.append(skipCertificateValidation);
577
578    final Control[] controls = getControls();
579    if (controls.length > 0)
580    {
581      buffer.append(", controls={");
582      for (int i=0; i < controls.length; i++)
583      {
584        if (i > 0)
585        {
586          buffer.append(", ");
587        }
588
589        buffer.append(controls[i]);
590      }
591      buffer.append('}');
592    }
593
594    buffer.append(')');
595  }
596}