001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-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) 2020-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;
037
038
039
040import java.io.ByteArrayInputStream;
041import java.io.File;
042import java.io.IOException;
043import java.io.Serializable;
044import java.security.cert.CertificateException;
045import java.security.cert.X509Certificate;
046import java.util.Collections;
047import java.util.HashSet;
048import java.util.Set;
049import java.util.concurrent.TimeUnit;
050import java.util.concurrent.atomic.AtomicLong;
051import java.util.concurrent.atomic.AtomicReference;
052import javax.net.ssl.X509TrustManager;
053import javax.security.auth.x500.X500Principal;
054
055import com.unboundid.ldap.sdk.Attribute;
056import com.unboundid.ldap.sdk.Entry;
057import com.unboundid.ldif.LDIFException;
058import com.unboundid.ldif.LDIFReader;
059import com.unboundid.util.Base64;
060import com.unboundid.util.CryptoHelper;
061import com.unboundid.util.Debug;
062import com.unboundid.util.NotNull;
063import com.unboundid.util.StaticUtils;
064import com.unboundid.util.ThreadSafety;
065import com.unboundid.util.ThreadSafetyLevel;
066
067import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
068
069
070
071/**
072 * This class provides an implementation of an X.509 trust manager that can be
073 * used to trust certificates listed in the topology registry of a Ping Identity
074 * Directory Server instance.  It will read the topology registry from the
075 * server's configuration file rather than communicating with it over LDAP, so
076 * it is only available for use when run from LDAP tools provided with the
077 * Ping Identity Directory Server.
078 * <BR>
079 * <BLOCKQUOTE>
080 *   <B>NOTE:</B>  This class, and other classes within the
081 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
082 *   supported for use against Ping Identity, UnboundID, and
083 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
084 *   for proprietary functionality or for external specifications that are not
085 *   considered stable or mature enough to be guaranteed to work in an
086 *   interoperable way with other types of LDAP servers.
087 * </BLOCKQUOTE>
088 */
089@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
090public final class TopologyRegistryTrustManager
091       implements X509TrustManager, Serializable
092{
093  /**
094   * The name of the object class that will be used in entries that may provide
095   * information about inter-server certificates in the topology registry.
096   */
097  @NotNull private static final String INTER_SERVER_CERT_OC =
098       "ds-cfg-server-instance";
099
100
101
102  /**
103   * The name of the attribute type for attributes that provide information
104   * about inter-server certificates in the topology registry.
105   */
106  @NotNull private static final String INTER_SERVER_CERT_AT =
107       "ds-cfg-inter-server-certificate";
108
109
110
111  /**
112   * The name of the object class that will be used in entries that may provide
113   * information about listener certificates in the topology registry.
114   */
115  @NotNull private static final String LISTENER_CERT_OC =
116       "ds-cfg-server-instance-listener";
117
118
119
120  /**
121   * The name of the attribute type for attributes that provide information
122   * about listener certificates in the topology registry.
123   */
124  @NotNull private static final String LISTENER_CERT_AT =
125       "ds-cfg-listener-certificate";
126
127
128
129  /**
130   * A pre-allocated empty certificate array.
131   */
132  @NotNull static final X509Certificate[] NO_CERTIFICATES =
133       new X509Certificate[0];
134
135
136
137  /**
138   * The serial version UID for this serializable class.
139   */
140  private static final long serialVersionUID = -1535917071172094611L;
141
142
143
144  // The time that the cached certificates will expire.
145  @NotNull private final AtomicLong cacheExpirationTime;
146
147  // The certificates that have been cached.
148  @NotNull private final AtomicReference<Set<X509Certificate>>
149       cachedCertificates;
150
151  // Indicates whether to ignore the validity window for issuer certificates
152  // when determining whether to trust a certificate chain.
153  private final boolean ignoreIssuerCertificateValidityWindow;
154
155  // Indicates whether to ignore the validity window for the peer certificate
156  // when determining whether to trust a certificate chain.
157  private final boolean ignorePeerCertificateValidityWindow;
158
159  // Indicates whether to require the peer certificate itself to be included in
160  // the topology registry for a certificate chain to be trusted.
161  private final boolean requirePeerCertificateInTopologyRegistry;
162
163  // The configuration file from which the certificate records will be read.
164  @NotNull private final File configurationFile;
165
166  // The maximum length of time in milliseconds that previously loaded
167  // certificates may be cached.
168  private final long cacheDurationMillis;
169
170
171
172  /**
173   * Creates a new instance of this trust manager with the provided settings.
174   *
175   * @param  configurationFile    The configuration file for the Ping Identity
176   *                              Directory Server instance that holds the
177   *                              topology registry data.  It must not be
178   *                              {@code null}.
179   * @param  cacheDurationMillis  The maximum length of time in milliseconds
180   *                              that previously loaded certificates may be
181   *                              cached.  If this is less than or equal to
182   *                              zero, then certificates will not be cached.
183   */
184  public TopologyRegistryTrustManager(@NotNull final File configurationFile,
185                                      final long cacheDurationMillis)
186  {
187    this(getDefaultProperties(configurationFile, cacheDurationMillis));
188  }
189
190
191
192  /**
193   * Retrieves the topology registry trust manager properties that should be
194   * used with the given configuration file and cache duration.
195   *
196   * @param  configurationFile    The configuration file for the Ping Identity
197   *                              Directory Server instance that holds the
198   *                              topology registry data.  It must not be
199   *                              {@code null}.
200   * @param  cacheDurationMillis  The maximum length of time in milliseconds
201   *                              that previously loaded certificates may be
202   *                              cached.  If this is less than or equal to
203   *                              zero, then certificates will not be cached.
204   *
205   * @return  The topology registry trust manager configuration properties that
206   *          should be used.
207   */
208  @NotNull()
209  private static TopologyRegistryTrustManagerProperties getDefaultProperties(
210               @NotNull final File configurationFile,
211               final long cacheDurationMillis)
212  {
213    final TopologyRegistryTrustManagerProperties properties =
214         new TopologyRegistryTrustManagerProperties(configurationFile);
215    properties.setCacheDuration(cacheDurationMillis, TimeUnit.MILLISECONDS);
216    return properties;
217  }
218
219
220
221  /**
222   * Creates a new instance of this trust manager with the provided properties.
223   *
224   * @param  properties  The properties to use to create this trust manager.
225   *                     It must not be {@code null}.
226   */
227  public TopologyRegistryTrustManager(
228              @NotNull final TopologyRegistryTrustManagerProperties properties)
229  {
230    configurationFile = properties.getConfigurationFile();
231    cacheDurationMillis = properties.getCacheDurationMillis();
232    requirePeerCertificateInTopologyRegistry =
233         properties.requirePeerCertificateInTopologyRegistry();
234    ignorePeerCertificateValidityWindow =
235         properties.ignorePeerCertificateValidityWindow();
236    ignoreIssuerCertificateValidityWindow =
237         properties.ignoreIssuerCertificateValidityWindow();
238
239    cacheExpirationTime = new AtomicLong(0L);
240    cachedCertificates = new AtomicReference<>(
241         Collections.<X509Certificate>emptySet());
242  }
243
244
245
246  /**
247   * Retrieves the server configuration file from which the topology registry
248   * certificates will be read.
249   *
250   * @return  The server configuration file from which the topology registry
251   *          certificates will be read.
252   */
253  @NotNull()
254  public File getConfigurationFile()
255  {
256    return configurationFile;
257  }
258
259
260
261  /**
262   * Retrieves the maximum length of time in milliseconds that cached topology
263   * registry information should be considered valid.
264   *
265   * @return  The maximum length of time in milliseconds that cached topology
266   *          registry information should be considered valid, or zero if
267   *          topology registry information should not be cached.
268   */
269  public long getCacheDurationMillis()
270  {
271    return cacheDurationMillis;
272  }
273
274
275
276  /**
277   * Indicates whether to require the peer certificate itself to be included in
278   * the topology registry for a certificate chain to be trusted.
279   *
280   * @return  {@code true} if a certificate chain may only be trusted if the
281   *          topology registry includes the peer certificate itself, or
282   *          {@code false} if a certificate chain may be trusted if the
283   *          topology registry contains the peer certificate or any of its
284   *          issuers.
285   */
286  public boolean requirePeerCertificateInTopologyRegistry()
287  {
288    return requirePeerCertificateInTopologyRegistry;
289  }
290
291
292
293  /**
294   * Indicates whether to ignore the validity window for the peer certificate
295   * when determining whether to trust a certificate chain.
296   *
297   * @return  {@code true} if a certificate chain may be considered trusted
298   *          even if the current time is outside the peer certificate's
299   *          validity window, or {@code false} if a certificate chain may only
300   *          be considered trusted if the current time is between the
301   *          {@code notBefore} and {@code notAfter} timestamps for the peer
302   *          certificate.
303   */
304  public boolean ignorePeerCertificateValidityWindow()
305  {
306    return ignorePeerCertificateValidityWindow;
307  }
308
309
310
311  /**
312   * Indicates whether to ignore the validity window for issuer certificates
313   * when determining whether to trust a certificate chain.
314   *
315   * @return  {@code true} if a certificate chain may be considered trusted
316   *          even if the current time is outside the any issuer certificate's
317   *          validity window, or {@code false} if a certificate chain may only
318   *          be considered trusted if the current time is between the
319   *          {@code notBefore} and {@code notAfter} timestamps for all issuer
320   *          certificates.
321   */
322  public boolean ignoreIssuerCertificateValidityWindow()
323  {
324    return ignoreIssuerCertificateValidityWindow;
325  }
326
327
328
329  /**
330   * Checks to determine whether the provided client certificate chain should be
331   * trusted.
332   *
333   * @param  chain     The client certificate chain for which to make the
334   *                   determination.
335   * @param  authType  The authentication type based on the client certificate.
336   *
337   * @throws  CertificateException  If the provided client certificate chain
338   *                                should not be trusted.
339   */
340  @Override()
341  public void checkClientTrusted(@NotNull final X509Certificate[] chain,
342                                 @NotNull final String authType)
343       throws CertificateException
344  {
345    checkTrusted(chain);
346  }
347
348
349
350  /**
351   * Checks to determine whether the provided server certificate chain should be
352   * trusted.
353   *
354   * @param  chain     The server certificate chain for which to make the
355   *                   determination.
356   * @param  authType  The key exchange algorithm used.
357   *
358   * @throws  CertificateException  If the provided server certificate chain
359   *                                should not be trusted.
360   */
361  @Override()
362  public void checkServerTrusted(@NotNull final X509Certificate[] chain,
363                                 @NotNull final String authType)
364       throws CertificateException
365  {
366    checkTrusted(chain);
367  }
368
369
370
371  /**
372   * Ensures that the provided certificate chain should be trusted.
373   *
374   * @param  chain  The certificate chain to validated.
375   *
376   * @throws  CertificateException  If the certificate chain should not be
377   *                                trusted.
378   */
379  private void checkTrusted(@NotNull final X509Certificate[] chain)
380          throws CertificateException
381  {
382    // Make sure that the chain is not null or empty.
383    if ((chain == null) || (chain.length == 0))
384    {
385      throw new CertificateException(ERR_TR_TM_NO_CHAIN.get());
386    }
387
388
389    // If appropriate, validate that the peer certificate is currently within
390    // its validity window.
391    final long currentTime = System.currentTimeMillis();
392    final X509Certificate peerCert = chain[0];
393    if (! ignorePeerCertificateValidityWindow)
394    {
395      if (currentTime < peerCert.getNotBefore().getTime())
396      {
397        throw new CertificateException(ERR_TR_TM_PEER_NOT_YET_VALID.get(
398             peerCert.getSubjectX500Principal().getName(X500Principal.RFC2253),
399             String.valueOf(peerCert.getNotBefore())));
400      }
401
402      if (currentTime > peerCert.getNotAfter().getTime())
403      {
404        throw new CertificateException(ERR_TR_TM_PEER_EXPIRED.get(
405             peerCert.getSubjectX500Principal().getName(X500Principal.RFC2253),
406             String.valueOf(peerCert.getNotAfter())));
407      }
408    }
409
410
411    // If appropriate, validate that all of the issuer certificates are also
412    // within their validity windows.
413    if (! ignoreIssuerCertificateValidityWindow)
414    {
415      for (int i=1; i < chain.length; i++)
416      {
417        final X509Certificate issuerCert = chain[i];
418        if (currentTime < issuerCert.getNotBefore().getTime())
419        {
420          throw new CertificateException(ERR_TR_TM_ISSUER_NOT_YET_VALID.get(
421               peerCert.getSubjectX500Principal().getName(
422                    X500Principal.RFC2253),
423               issuerCert.getSubjectX500Principal().getName(
424                    X500Principal.RFC2253),
425               String.valueOf(peerCert.getNotBefore())));
426        }
427
428        if (currentTime > issuerCert.getNotAfter().getTime())
429        {
430          throw new CertificateException(ERR_TR_TM_ISSUER_EXPIRED.get(
431               peerCert.getSubjectX500Principal().getName(
432                    X500Principal.RFC2253),
433               issuerCert.getSubjectX500Principal().getName(
434                    X500Principal.RFC2253),
435               String.valueOf(peerCert.getNotAfter())));
436        }
437      }
438    }
439
440
441    // If the cache is valid, then consult it to determine whether we should
442    // trust the certificate chain.
443    final Set<X509Certificate> cachedCerts = cachedCertificates.get();
444    if ((! cachedCerts.isEmpty()) && (cacheExpirationTime.get() >= currentTime))
445    {
446      if (mayTrustChainBasedOnCertificateSet(chain, cachedCerts))
447      {
448        return;
449      }
450    }
451
452
453    // If we've gotten here, then either caching is disabled, the cache is
454    // expired, or the presented chain can't be trusted based on the cached
455    // information.  In any case, read the configuration and extract all
456    // certificates from the topology registry.
457    final Set<X509Certificate> topologyRegistryCertificates =
458         readTopologyRegistryCertificates();
459
460
461    // If we should cache topology registry data, then update it with the set
462    // of certificates we just read.
463    if (cacheDurationMillis > 0L)
464    {
465      cachedCertificates.set(topologyRegistryCertificates);
466      cacheExpirationTime.set(currentTime + cacheDurationMillis);
467    }
468
469
470    // Check to see if we should trust the certificate chain based on the
471    // topology registry data we just read.
472    if (mayTrustChainBasedOnCertificateSet(chain, topologyRegistryCertificates))
473    {
474      return;
475    }
476
477
478    // If we've gotten here, then the chain can't be considered trusted.
479    if ((requirePeerCertificateInTopologyRegistry) || (chain.length == 1))
480    {
481      throw new CertificateException(ERR_TP_TM_PEER_NOT_FOUND.get(
482           peerCert.getSubjectX500Principal().getName(X500Principal.RFC2253)));
483    }
484    else
485    {
486      throw new CertificateException(ERR_TP_TM_PEER_OR_ISSUERS_NOT_FOUND.get(
487           peerCert.getSubjectX500Principal().getName(X500Principal.RFC2253)));
488    }
489  }
490
491
492
493  /**
494   * Indicates whether the provided certificate chain may be considered
495   * trusted using the given set of trusted certificates.
496   *
497   * @param  chain           The certificate chain for which to make the
498   *                         determination. It must not be {@code null} or
499   *                         empty.
500   * @param  certificateSet  The set of trusted certificates to use in making
501   *                         the determination.  It must not be {@code null}.
502   *
503   * @return  {@code true} if the presented certificate chain may be considered
504   *          trusted using the given set of trusted certificates, or
505   *          {@code false} if not.
506   */
507  private boolean mayTrustChainBasedOnCertificateSet(
508               @NotNull final X509Certificate[] chain,
509               @NotNull final Set<X509Certificate> certificateSet)
510  {
511    // First, check the peer certificate.
512    if (certificateSet.contains(chain[0]))
513    {
514      return true;
515    }
516
517
518    // If we don't require the peer certificate itself to be present in the
519    // topology registry, then check its issuer certificates.
520    if (! requirePeerCertificateInTopologyRegistry)
521    {
522      for (int i=1; i < chain.length; i++)
523      {
524        if (certificateSet.contains(chain[i]))
525        {
526          return true;
527        }
528      }
529    }
530
531
532    // If we've gotten here, then we can't trust the certificate chain based on
533    // information in the provided set of trusted certificates.
534    return false;
535  }
536
537
538
539  /**
540   * Reads the certificates defined in the topology registry.
541   *
542   * @return  A set containing the certificates defined in the topology
543   *          registry, or an empty set if no certificates are found.
544   *
545   * @throws  CertificateException  If a problem is encountered while reading
546   *                                certificates from the topology registry.
547   */
548  @NotNull()
549  private Set<X509Certificate> readTopologyRegistryCertificates()
550          throws CertificateException
551  {
552    try (LDIFReader ldifReader = new LDIFReader(configurationFile))
553    {
554      final Set<X509Certificate> certs = new HashSet<>();
555      while (true)
556      {
557        final Entry entry;
558        try
559        {
560          entry = ldifReader.readEntry();
561        }
562        catch (final LDIFException e)
563        {
564          Debug.debugException(e);
565          if (e.mayContinueReading())
566          {
567            continue;
568          }
569          else
570          {
571            throw new CertificateException(
572                 ERR_TP_TM_MALFORMED_CONFIG.get(
573                      configurationFile.getAbsolutePath(),
574                      StaticUtils.getExceptionMessage(e)),
575                 e);
576          }
577        }
578
579        if (entry == null)
580        {
581          return Collections.unmodifiableSet(certs);
582        }
583
584        if (entry.hasObjectClass(INTER_SERVER_CERT_OC) &&
585             entry.hasAttribute(INTER_SERVER_CERT_AT))
586        {
587          parseCertificates(certs, entry.getAttribute(INTER_SERVER_CERT_AT));
588        }
589        else if (entry.hasObjectClass(LISTENER_CERT_OC) &&
590             entry.hasAttribute(LISTENER_CERT_AT))
591        {
592          parseCertificates(certs, entry.getAttribute(LISTENER_CERT_AT));
593        }
594      }
595    }
596    catch (final IOException e)
597    {
598      Debug.debugException(e);
599      throw new CertificateException(
600           ERR_TP_TM_ERROR_READING_CONFIG_FILE.get(
601                configurationFile.getAbsolutePath(),
602                StaticUtils.getExceptionMessage(e)),
603           e);
604    }
605  }
606
607
608
609  /**
610   * Parses any values of the provided attribute as a set of X.509 certificates.
611   *
612   * @param  certs  The set that should be updated with the certificates that
613   *                are parsed.
614   * @param  attr   The attribute whose values should be parsed.
615   */
616  private void parseCertificates(@NotNull final Set<X509Certificate> certs,
617                                 @NotNull final Attribute attr)
618  {
619    final StringBuilder certBase64 = new StringBuilder();
620    for (final String value : attr.getValues())
621    {
622      try
623      {
624        for (final String line : StaticUtils.stringToLines(value))
625        {
626          if (line.equalsIgnoreCase("-----BEGIN CERTIFICATE-----"))
627          {
628            continue;
629          }
630          else if (line.equalsIgnoreCase("-----END CERTIFICATE-----"))
631          {
632            final byte[] certBytes = Base64.decode(certBase64.toString());
633            certBase64.setLength(0);
634
635            certs.add((X509Certificate) CryptoHelper.getCertificateFactory(
636                 "X.509").generateCertificate(new ByteArrayInputStream(
637                      certBytes)));
638          }
639          else
640          {
641            certBase64.append(line);
642          }
643        }
644      }
645      catch (final Exception e)
646      {
647        Debug.debugException(e);
648      }
649    }
650  }
651
652
653
654  /**
655   * Retrieves the accepted issuer certificates for this trust manager.
656   *
657   * @return  The accepted issuer certificates for this trust manager, or an
658   *          empty set of accepted issuers if a problem was encountered while
659   *          initializing this trust manager.
660   */
661  @Override()
662  @NotNull()
663  public X509Certificate[] getAcceptedIssuers()
664  {
665    return NO_CERTIFICATES;
666  }
667
668
669
670  /**
671   * Retrieves a string representation of this topology registry trust manager
672   * instance.
673   *
674   * @return  A string representation of this topology registry trust manager
675   *          instance.
676   */
677  @Override()
678  @NotNull()
679  public String toString()
680  {
681    final StringBuilder buffer = new StringBuilder();
682    toString(buffer);
683    return buffer.toString();
684  }
685
686
687
688  /**
689   * Appends a string representation of this topology registry trust manager
690   * instance to the given buffer.
691   *
692   * @param  buffer  The buffer to which the string representation should be
693   *                 appended.
694   */
695  public void toString(@NotNull final StringBuilder buffer)
696  {
697    buffer.append("TopologyRegistryTrustManager(configurationFile='");
698    buffer.append(configurationFile.getAbsolutePath());
699    buffer.append("', cacheDurationMillis=");
700    buffer.append(cacheDurationMillis);
701    buffer.append(", requirePeerCertificateInTopologyRegistry=");
702    buffer.append(requirePeerCertificateInTopologyRegistry);
703    buffer.append(", ignorePeerCertificateValidityWindow=");
704    buffer.append(ignorePeerCertificateValidityWindow);
705    buffer.append(", ignoreIssuerCertificateValidityWindow=");
706    buffer.append(ignoreIssuerCertificateValidityWindow);
707    buffer.append(')');
708  }
709}