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}