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.util.ssl; 037 038 039 040import java.io.File; 041import java.io.IOException; 042import java.io.Serializable; 043import java.net.Socket; 044import java.security.KeyStoreException; 045import java.security.MessageDigest; 046import java.security.Principal; 047import java.security.PrivateKey; 048import java.security.cert.X509Certificate; 049import java.util.ArrayList; 050import java.util.Arrays; 051import java.util.Collections; 052import java.util.List; 053import java.util.logging.Level; 054import javax.net.ssl.X509KeyManager; 055 056import com.unboundid.ldap.sdk.DN; 057import com.unboundid.util.CryptoHelper; 058import com.unboundid.util.Debug; 059import com.unboundid.util.DebugType; 060import com.unboundid.util.NotMutable; 061import com.unboundid.util.NotNull; 062import com.unboundid.util.Nullable; 063import com.unboundid.util.StaticUtils; 064import com.unboundid.util.ThreadSafety; 065import com.unboundid.util.ThreadSafetyLevel; 066import com.unboundid.util.Validator; 067import com.unboundid.util.ssl.cert.CertException; 068import com.unboundid.util.ssl.cert.PKCS8PEMFileReader; 069import com.unboundid.util.ssl.cert.PKCS8PrivateKey; 070import com.unboundid.util.ssl.cert.X509PEMFileReader; 071 072import static com.unboundid.util.ssl.SSLMessages.*; 073 074 075 076/** 077 * This class provides an implementation of an X.509 key manager that can obtain 078 * a certificate chain and private key from PEM files. This key manager will 079 * only support a single entry, and the alias for that entry will be a SHA-256 080 * fingerprint for the certificate. However, the certificate can be retrieved 081 * with any (or no) alias. 082 */ 083@NotMutable() 084@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 085public final class PEMFileKeyManager 086 implements X509KeyManager, Serializable 087{ 088 /** 089 * The serial version UID for this serializable class. 090 */ 091 private static final long serialVersionUID = 1973401278035832777L; 092 093 094 095 /** 096 * The name of the digest algorithm that will be used to generate a 097 * certificate fingerprint for use as the alias. 098 */ 099 @NotNull private static final String ALIAS_FINGERPRINT_ALGORITHM = "SHA-256"; 100 101 102 103 // The certificate chain read from PEM files. 104 @NotNull private final X509Certificate[] certificateChain; 105 106 // The private key read from a PEM file. 107 @NotNull private final PrivateKey privateKey; 108 109 // The alias that will be used for the certificate chain. 110 @NotNull private final String alias; 111 112 113 114 /** 115 * Creates a new instance of this key manager with the provided PEM files. 116 * 117 * @param certificateChainPEMFile 118 * The file containing the PEM-formatted X.509 representations of 119 * the certificates in the certificate chain. This must not be 120 * {@code null}, the file must exist, and it must contain at 121 * least one certificate (the end entity certificate), but may 122 * contain additional certificates as needed for the complete 123 * certificate chain. Certificates should be ordered such that 124 * the first certificate must be the end entity certificate, and 125 * each subsequent certificate must be the issuer for the 126 * previous certificate. The chain does not need to be complete 127 * as long as the peer may be expected to have prior knowledge of 128 * any missing issuer certificates. 129 * @param privateKeyPEMFile 130 * The file containing the PEM-formatted PKCS #8 representation 131 * of the private key for the end entity certificate. This must 132 * not be {@code null}, the file must exist, and it must contain 133 * exactly one PEM-encoded private key. The private key must not 134 * be encrypted. 135 * 136 * @throws KeyStoreException If there is a problem with any of the provided 137 * PEM files. 138 */ 139 public PEMFileKeyManager(@NotNull final File certificateChainPEMFile, 140 @NotNull final File privateKeyPEMFile) 141 throws KeyStoreException 142 { 143 this(Collections.singletonList(certificateChainPEMFile), privateKeyPEMFile); 144 } 145 146 147 148 /** 149 * Creates a new instance of this key manager with the provided PEM files. 150 * 151 * @param certificateChainPEMFile 152 * The file containing the PEM-formatted X.509 representations of 153 * the certificates in the certificate chain. This must not be 154 * {@code null}, the file must exist, and it must contain at 155 * least one certificate (the end entity certificate), but may 156 * contain additional certificates as needed for the complete 157 * certificate chain. Certificates should be ordered such that 158 * the first certificate must be the end entity certificate, and 159 * each subsequent certificate must be the issuer for the 160 * previous certificate. The chain does not need to be complete 161 * as long as the peer may be expected to have prior knowledge of 162 * any missing issuer certificates. 163 * @param privateKeyPEMFile 164 * The file containing the PEM-formatted PKCS #8 representation 165 * of the private key for the end entity certificate. This must 166 * not be {@code null}, the file must exist, and it must contain 167 * exactly one PEM-encoded private key. The private key may 168 * optionally be encrypted. 169 * @param privateKeyEncryptionPassword 170 * The password needed to decrypt the private key if it is 171 * encrypted. This may be {@code null} if the private key is not 172 * encrypted. 173 * 174 * @throws KeyStoreException If there is a problem with any of the provided 175 * PEM files. 176 */ 177 public PEMFileKeyManager(@NotNull final File certificateChainPEMFile, 178 @NotNull final File privateKeyPEMFile, 179 @Nullable final char[] privateKeyEncryptionPassword) 180 throws KeyStoreException 181 { 182 this(Collections.singletonList(certificateChainPEMFile), privateKeyPEMFile, 183 privateKeyEncryptionPassword); 184 } 185 186 187 188 /** 189 * Creates a new instance of this key manager with the provided PEM files. 190 * 191 * @param certificateChainPEMFiles 192 * The files containing the PEM-formatted X.509 representations 193 * of the certificates in the certificate chain. This must not 194 * be {@code null} or empty. Each file must exist and must 195 * contain at least one certificate. The files will be processed 196 * in the order in which they are provided. The first 197 * certificate in the first file must be the end entity 198 * certificate, and each subsequent certificate must be the 199 * issuer for the previous certificate. The chain does not need 200 * to be complete as long as the peer may be expected to have 201 * prior knowledge of any missing issuer certificates. 202 * @param privateKeyPEMFile 203 * The file containing the PEM-formatted PKCS #8 representation 204 * of the private key for the end entity certificate. This must 205 * not be {@code null}, the file must exist, and it must contain 206 * exactly one PEM-encoded private key. The private key must not 207 * be encrypted. 208 * 209 * @throws KeyStoreException If there is a problem with any of the provided 210 * PEM files. 211 */ 212 public PEMFileKeyManager(@NotNull final File[] certificateChainPEMFiles, 213 @NotNull final File privateKeyPEMFile) 214 throws KeyStoreException 215 { 216 this(StaticUtils.toList(certificateChainPEMFiles), privateKeyPEMFile); 217 } 218 219 220 221 /** 222 * Creates a new instance of this key manager with the provided PEM files. 223 * 224 * @param certificateChainPEMFiles 225 * The files containing the PEM-formatted X.509 representations 226 * of the certificates in the certificate chain. This must not 227 * be {@code null} or empty. Each file must exist and must 228 * contain at least one certificate. The files will be processed 229 * in the order in which they are provided. The first 230 * certificate in the first file must be the end entity 231 * certificate, and each subsequent certificate must be the 232 * issuer for the previous certificate. The chain does not need 233 * to be complete as long as the peer may be expected to have 234 * prior knowledge of any missing issuer certificates. 235 * @param privateKeyPEMFile 236 * The file containing the PEM-formatted PKCS #8 representation 237 * of the private key for the end entity certificate. This must 238 * not be {@code null}, the file must exist, and it must contain 239 * exactly one PEM-encoded private key. The private key may 240 * optionally be encrypted. 241 * @param privateKeyEncryptionPassword 242 * The password needed to decrypt the private key if it is 243 * encrypted. This may be {@code null} if the private key is not 244 * encrypted. 245 * 246 * @throws KeyStoreException If there is a problem with any of the provided 247 * PEM files. 248 */ 249 public PEMFileKeyManager(@NotNull final File[] certificateChainPEMFiles, 250 @NotNull final File privateKeyPEMFile, 251 @Nullable final char[] privateKeyEncryptionPassword) 252 throws KeyStoreException 253 { 254 this(StaticUtils.toList(certificateChainPEMFiles), privateKeyPEMFile, 255 privateKeyEncryptionPassword); 256 } 257 258 259 260 /** 261 * Creates a new instance of this key manager with the provided PEM files. 262 * 263 * @param certificateChainPEMFiles 264 * The files containing the PEM-formatted X.509 representations 265 * of the certificates in the certificate chain. This must not 266 * be {@code null} or empty. Each file must exist and must 267 * contain at least one certificate. The files will be processed 268 * in the order in which they are provided. The first 269 * certificate in the first file must be the end entity 270 * certificate, and each subsequent certificate must be the 271 * issuer for the previous certificate. The chain does not need 272 * to be complete as long as the peer may be expected to have 273 * prior knowledge of any missing issuer certificates. 274 * @param privateKeyPEMFile 275 * The file containing the PEM-formatted PKCS #8 representation 276 * of the private key for the end entity certificate. This must 277 * not be {@code null}, the file must exist, and it must contain 278 * exactly one PEM-encoded private key. The private key must not 279 * be encrypted. 280 * 281 * @throws KeyStoreException If there is a problem with any of the provided 282 * PEM files. 283 */ 284 public PEMFileKeyManager(@NotNull final List<File> certificateChainPEMFiles, 285 @NotNull final File privateKeyPEMFile) 286 throws KeyStoreException 287 { 288 this(certificateChainPEMFiles, privateKeyPEMFile, null); 289 } 290 291 292 293 /** 294 * Creates a new instance of this key manager with the provided PEM files. 295 * 296 * @param certificateChainPEMFiles 297 * The files containing the PEM-formatted X.509 representations 298 * of the certificates in the certificate chain. This must not 299 * be {@code null} or empty. Each file must exist and must 300 * contain at least one certificate. The files will be processed 301 * in the order in which they are provided. The first 302 * certificate in the first file must be the end entity 303 * certificate, and each subsequent certificate must be the 304 * issuer for the previous certificate. The chain does not need 305 * to be complete as long as the peer may be expected to have 306 * prior knowledge of any missing issuer certificates. 307 * @param privateKeyPEMFile 308 * The file containing the PEM-formatted PKCS #8 representation 309 * of the private key for the end entity certificate. This must 310 * not be {@code null}, the file must exist, and it must contain 311 * exactly one PEM-encoded private key. The private key may 312 * optionally be encrypted. 313 * @param privateKeyEncryptionPassword 314 * The password needed to decrypt the private key if it is 315 * encrypted. This may be {@code null} if the private key is not 316 * encrypted. 317 * 318 * @throws KeyStoreException If there is a problem with any of the provided 319 * PEM files. 320 */ 321 public PEMFileKeyManager(@NotNull final List<File> certificateChainPEMFiles, 322 @NotNull final File privateKeyPEMFile, 323 @Nullable final char[] privateKeyEncryptionPassword) 324 throws KeyStoreException 325 { 326 Validator.ensureNotNullWithMessage(certificateChainPEMFiles, 327 "PEMFileKeyManager.certificateChainPEMFiles must not be null."); 328 Validator.ensureFalse(certificateChainPEMFiles.isEmpty(), 329 "PEMFileKeyManager.certificateChainPEMFiles must not be empty."); 330 Validator.ensureNotNullWithMessage(privateKeyPEMFile, 331 "PEMFileKeyManager.privateKeyPEMFile must not be null."); 332 333 certificateChain = readCertificateChain(certificateChainPEMFiles); 334 privateKey = readPrivateKey(privateKeyPEMFile, 335 privateKeyEncryptionPassword); 336 337 338 // Compute a SHA-256 fingerprint for the certificate to use as the alias. 339 try 340 { 341 final MessageDigest sha256 = 342 CryptoHelper.getMessageDigest(ALIAS_FINGERPRINT_ALGORITHM); 343 final byte[] digestBytes = 344 sha256.digest(certificateChain[0].getEncoded()); 345 alias = StaticUtils.toHex(digestBytes); 346 } 347 catch (final Exception e) 348 { 349 Debug.debugException(e); 350 throw new KeyStoreException( 351 ERR_PEM_FILE_KEY_MANAGER_CANNOT_COMPUTE_ALIAS.get( 352 ALIAS_FINGERPRINT_ALGORITHM, 353 StaticUtils.getExceptionMessage(e)), 354 e); 355 } 356 } 357 358 359 360 /** 361 * Reads the certificate chain from the provided PEM files. 362 * 363 * @param certificateChainPEMFiles The files containing the PEM-formatted 364 * X.509 representations of the certificates 365 * in the certificate chain. This must not 366 * be {@code null} or empty. Each file must 367 * exist and must contain at least one 368 * certificate. The files will be processed 369 * in the order in which they are provided. 370 * The first certificate in the first file 371 * must be the end entity certificate, and 372 * each subsequent certificate must be the 373 * issuer for the previous certificate. The 374 * chain does not need to be complete as 375 * long as the peer may be expected to have 376 * prior knowledge of any missing issuer 377 * certificates. 378 * 379 * @return The certificate chain that was read. 380 * 381 * @throws KeyStoreException If a problem is encountered while reading the 382 * certificate chain. 383 */ 384 @NotNull() 385 private static X509Certificate[] readCertificateChain( 386 @NotNull final List<File> certificateChainPEMFiles) 387 throws KeyStoreException 388 { 389 com.unboundid.util.ssl.cert.X509Certificate lastCert = null; 390 391 final List<X509Certificate> certList = new ArrayList<>(); 392 for (final File f : certificateChainPEMFiles) 393 { 394 if (! f.exists()) 395 { 396 throw new KeyStoreException( 397 ERR_PEM_FILE_KEY_MANAGER_NO_SUCH_CERT_FILE.get( 398 f.getAbsolutePath())); 399 } 400 401 boolean readCert = false; 402 try (final X509PEMFileReader r = new X509PEMFileReader(f)) 403 { 404 while (true) 405 { 406 final com.unboundid.util.ssl.cert.X509Certificate c = 407 r.readCertificate(); 408 if (c == null) 409 { 410 if (! readCert) 411 { 412 throw new KeyStoreException( 413 ERR_PEM_FILE_KEY_MANAGER_EMPTY_CERT_FILE.get( 414 f.getAbsolutePath())); 415 } 416 417 break; 418 } 419 420 readCert = true; 421 if ((lastCert != null) && (! c.isIssuerFor(lastCert))) 422 { 423 throw new KeyStoreException( 424 ERR_PEM_FILE_KEY_MANAGER_SUBSEQUENT_CERT_NOT_ISSUER.get( 425 c.getSubjectDN().toString(), f.getAbsolutePath(), 426 lastCert.getSubjectDN().toString())); 427 } 428 429 try 430 { 431 certList.add((X509Certificate) c.toCertificate()); 432 } 433 catch (final Exception e) 434 { 435 Debug.debugException(e); 436 throw new KeyStoreException( 437 ERR_PEM_FILE_KEY_MANAGER_CANNOT_DECODE_CERT.get( 438 c.getSubjectDN().toString(), f.getAbsolutePath(), 439 StaticUtils.getExceptionMessage(e)), 440 e); 441 } 442 443 lastCert = c; 444 } 445 } 446 catch (final KeyStoreException e) 447 { 448 Debug.debugException(e); 449 throw e; 450 } 451 catch (final IOException e) 452 { 453 Debug.debugException(e); 454 throw new KeyStoreException( 455 ERR_PEM_FILE_KEY_MANAGER_ERROR_READING_FROM_FILE.get( 456 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)), 457 e); 458 } 459 catch (final CertException e) 460 { 461 Debug.debugException(e); 462 throw new KeyStoreException( 463 ERR_PEM_FILE_KEY_MANAGER_ERROR_READING_CERT.get( 464 f.getAbsolutePath(), e.getMessage()), 465 e); 466 } 467 } 468 469 final X509Certificate[] chain = new X509Certificate[certList.size()]; 470 return certList.toArray(chain); 471 } 472 473 474 475 /** 476 * Reads the private key from the provided PEM file. 477 * 478 * 479 * @param privateKeyPEMFile 480 * The file containing the PEM-formatted PKCS #8 representation 481 * of the private key for the end entity certificate. This must 482 * not be {@code null}, the file must exist, and it must contain 483 * exactly one PEM-encoded private key. The private key may 484 * optionally be encrypted. 485 * @param encryptionPassword 486 * The password needed to decrypt the private key if it is 487 * encrypted. This may be {@code null} if the private key is not 488 * encrypted. 489 * 490 * @return The private key that was read. 491 * 492 * @throws KeyStoreException If a problem is encountered while reading the 493 * certificate chain. 494 */ 495 @NotNull() 496 private static PrivateKey readPrivateKey( 497 @NotNull final File privateKeyPEMFile, 498 @Nullable final char[] encryptionPassword) 499 throws KeyStoreException 500 { 501 if (! privateKeyPEMFile.exists()) 502 { 503 throw new KeyStoreException( 504 ERR_PEM_FILE_KEY_MANAGER_NO_SUCH_KEY_FILE.get( 505 privateKeyPEMFile.getAbsolutePath())); 506 } 507 508 try (PKCS8PEMFileReader r = new PKCS8PEMFileReader(privateKeyPEMFile)) 509 { 510 final PKCS8PrivateKey privateKey = r.readPrivateKey(encryptionPassword); 511 if (privateKey == null) 512 { 513 throw new KeyStoreException( 514 ERR_PEM_FILE_KEY_MANAGER_EMPTY_KEY_FILE.get( 515 privateKeyPEMFile.getAbsolutePath())); 516 } 517 518 if (r.readPrivateKey(encryptionPassword) != null) 519 { 520 throw new KeyStoreException( 521 ERR_PEM_FILE_KEY_MANAGER_MULTIPLE_KEYS_IN_FILE.get( 522 privateKeyPEMFile.getAbsolutePath())); 523 } 524 525 try 526 { 527 return privateKey.toPrivateKey(); 528 } 529 catch (final Exception e) 530 { 531 Debug.debugException(e); 532 throw new KeyStoreException( 533 ERR_PEM_FILE_KEY_MANAGER_CANNOT_DECODE_KEY.get( 534 privateKeyPEMFile.getAbsolutePath(), 535 StaticUtils.getExceptionMessage(e)), 536 e); 537 } 538 } 539 catch (final KeyStoreException e) 540 { 541 Debug.debugException(e); 542 throw e; 543 } 544 catch (final IOException e) 545 { 546 Debug.debugException(e); 547 throw new KeyStoreException( 548 ERR_PEM_FILE_KEY_MANAGER_ERROR_READING_FROM_FILE.get( 549 privateKeyPEMFile.getAbsolutePath(), 550 StaticUtils.getExceptionMessage(e)), 551 e); 552 } 553 catch (final CertException e) 554 { 555 Debug.debugException(e); 556 throw new KeyStoreException( 557 ERR_PEM_FILE_KEY_MANAGER_ERROR_READING_KEY.get( 558 privateKeyPEMFile.getAbsolutePath(), e.getMessage()), 559 e); 560 } 561 } 562 563 564 565 /** 566 * Retrieves the aliases that may be used for a client certificate chain with 567 * the requested settings. 568 * 569 * @param keyType The key type for the alias to retrieve. It may be 570 * {@code null} if any key type may be used. 571 * @param issuers The set of allowed issuers for the aliases to retrieve. 572 * It may be {@code null} if any issuers should be allowed. 573 * 574 * @return An array of the aliases that may be used for a client certificate 575 * chain with the requested settings, or {@code null} if the 576 * certificate chain does not match the requested criteria. 577 */ 578 @Override() 579 @Nullable() 580 public String[] getClientAliases(@Nullable final String keyType, 581 @Nullable final Principal[] issuers) 582 { 583 return getAliases(keyType, issuers); 584 } 585 586 587 588 /** 589 * Retrieves the aliases that may be used for a server certificate chain with 590 * the requested settings. 591 * 592 * @param keyType The key type for the alias to retrieve. It may be 593 * {@code null} if any key type may be used. 594 * @param issuers The set of allowed issuers for the aliases to retrieve. 595 * It may be {@code null} if any issuers should be allowed. 596 * 597 * @return An array of the aliases that may be used for a server certificate 598 * chain with the requested settings, or {@code null} if the 599 * certificate chain does not match the requested criteria. 600 */ 601 @Override() 602 @Nullable() 603 public String[] getServerAliases(@Nullable final String keyType, 604 @Nullable final Principal[] issuers) 605 { 606 return getAliases(keyType, issuers); 607 } 608 609 610 611 /** 612 * Retrieves the aliases that may be used for a certificate chain with the 613 * requested settings. 614 * 615 * @param keyType The key type for the alias to retrieve. It may be 616 * {@code null} if any key type may be used. 617 * @param issuers A list of acceptable CA issuer subject names. It may be 618 * {@code null} if any issuers may be used. 619 * 620 * @return An array of the aliases that may be used for a certificate chain 621 * with the requested settings, or {@code null} if the certificate 622 * chain does not match the requested criteria. 623 */ 624 @Nullable() 625 private String[] getAliases(@Nullable final String keyType, 626 @Nullable final Principal[] issuers) 627 { 628 if (! hasKeyType(keyType)) 629 { 630 Debug.debug(Level.WARNING, DebugType.OTHER, 631 "PEMFileKeyManager.getAliases returning null because the " + 632 "requested keyType is '" + keyType + "' but the private " + 633 "key uses an algorithm of '" + privateKey.getAlgorithm() + 634 "'."); 635 return null; 636 } 637 638 if (! hasAnyIssuer(issuers)) 639 { 640 Debug.debug(Level.WARNING, DebugType.OTHER, 641 "PEMFileKeyManager.getAliases returning null because " + 642 "certificate chain " + Arrays.toString(certificateChain) + 643 " does not use any of the allowed issuers " + 644 Arrays.toString(issuers)); 645 646 return null; 647 } 648 649 return new String[] { alias }; 650 } 651 652 653 654 /** 655 * Chooses the alias that should be used for the preferred client certificate 656 * chain with the requested settings. 657 * 658 * @param keyTypes The set of allowed key types for the alias to retrieve. 659 * It may be {@code null} if any key type may be used. 660 * @param issuers The set of allowed issuers for the alias to retrieve. It 661 * may be {@code null} if any issuers should be allowed. 662 * @param socket The socket with which the certificate chain will be used. 663 * It may be {@code null} if no socket should be taken into 664 * consideration. 665 * 666 * @return The alias that should be used for the preferred client certificate 667 * chain with the requested settings, or {@code null} if there is no 668 * applicable alias. 669 */ 670 @Override() 671 @Nullable() 672 public String chooseClientAlias(@Nullable final String[] keyTypes, 673 @Nullable final Principal[] issuers, 674 @Nullable final Socket socket) 675 { 676 return chooseAlias(keyTypes, issuers); 677 } 678 679 680 681 /** 682 * Chooses the alias that should be used for the preferred server certificate 683 * chain with the requested settings. 684 * 685 * @param keyType The key type for the alias to retrieve. It may be 686 * {@code null} if any key type may be u sed. 687 * @param issuers The set of allowed issuers for the alias to retrieve. It 688 * may be {@code null} if any issuers should be allowed. 689 * @param socket The socket with which the certificate chain will be used. 690 * It may be {@code null} if no socket should be taken into 691 * consideration. 692 * 693 * @return The alias that should be used for the preferred server certificate 694 * chain with the requested settings, or {@code null} if there is no 695 * applicable alias. 696 */ 697 @Override() 698 @Nullable() 699 public String chooseServerAlias(@Nullable final String keyType, 700 @Nullable final Principal[] issuers, 701 @Nullable final Socket socket) 702 { 703 if (keyType == null) 704 { 705 return chooseAlias(null, issuers); 706 } 707 else 708 { 709 return chooseAlias(new String[] { keyType }, issuers); 710 } 711 } 712 713 714 715 /** 716 * Chooses the alias that should be used for the preferred certificate chain 717 * with the requested settings. 718 * 719 * @param keyTypes The set of allowed key types for the alias to retrieve. 720 * It may be {@code null} if any key type may be used. 721 * @param issuers The set of allowed issuers for the alias to retrieve. It 722 * may be {@code null} if any issuers should be allowed. 723 * 724 * @return The alias that should be used for the preferred certificate chain 725 * with the requested settings, or {@code null} if there is no 726 * applicable alias. 727 */ 728 @Nullable() 729 public String chooseAlias(@Nullable final String[] keyTypes, 730 @Nullable final Principal[] issuers) 731 { 732 if ((keyTypes != null) && (keyTypes.length > 0)) 733 { 734 boolean keyTypeFound = false; 735 for (final String keyType : keyTypes) 736 { 737 if (hasKeyType(keyType)) 738 { 739 keyTypeFound = true; 740 break; 741 } 742 } 743 744 if (! keyTypeFound) 745 { 746 Debug.debug(Level.WARNING, DebugType.OTHER, 747 "PEMFileKeyManager.chooseAlias returning null because " + 748 "certificate chain " + Arrays.toString(certificateChain) + 749 " uses a key type of " + privateKey.getAlgorithm() + 750 ", which does not match any of the allowed key types of " + 751 Arrays.toString(keyTypes)); 752 753 return null; 754 } 755 } 756 757 if (! hasAnyIssuer(issuers)) 758 { 759 Debug.debug(Level.WARNING, DebugType.OTHER, 760 "PEMFileKeyManager.chooseAlias returning null because " + 761 "certificate chain " + Arrays.toString(certificateChain) + 762 " does not use any of the allowed issuers " + 763 Arrays.toString(issuers)); 764 765 return null; 766 } 767 768 return alias; 769 } 770 771 772 773 /** 774 * Indicates whether the certificate chain has the specified key type. 775 * 776 * @param keyType The key type for which to make the determination. It may 777 * be {@code null} if the key type does not matter. 778 * 779 * @return {@code true} if the certificate chain has the specified key type 780 * (or if the key type does not matter), or {@code false} if not. 781 */ 782 private boolean hasKeyType(@Nullable final String keyType) 783 { 784 return ((keyType == null) || 785 privateKey.getAlgorithm().equalsIgnoreCase(keyType)); 786 } 787 788 789 790 /** 791 * Indicates whether the certificate chain has any of the issuers in the 792 * provided array. 793 * 794 * @param issuers The array of acceptable issuers. It may be 795 * {@code null} if the set of issuers does not matter. 796 * 797 * @return {@code true} if the certificate chain uses one of the accepted 798 * issuers (or if the issuers do not matter), or {@code false} if 799 * not. 800 */ 801 private boolean hasAnyIssuer(@Nullable final Principal[] issuers) 802 { 803 if ((issuers == null) || (issuers.length == 0)) 804 { 805 return true; 806 } 807 808 809 // Check all of the issuer certificates for the chain. 810 for (final Principal acceptableIssuer : issuers) 811 { 812 final String acceptableIssuerString = acceptableIssuer.toString(); 813 for (final X509Certificate c : certificateChain) 814 { 815 final Principal certificateIssuer = c.getIssuerDN(); 816 final String certificateIssuerString = certificateIssuer.toString(); 817 try 818 { 819 if (DN.equals(certificateIssuerString, acceptableIssuerString)) 820 { 821 return true; 822 } 823 } 824 catch (final Exception e) 825 { 826 Debug.debugException(e); 827 } 828 } 829 } 830 831 832 // Also check the subject DN for the first certificate in the chain. 833 final Principal endEntitySubject = certificateChain[0].getSubjectDN(); 834 final String endEntitySubjectString = endEntitySubject.toString(); 835 for (final Principal acceptableIssuer : issuers) 836 { 837 final String acceptableIssuerString = acceptableIssuer.toString(); 838 try 839 { 840 if (DN.equals(endEntitySubjectString, acceptableIssuerString)) 841 { 842 return true; 843 } 844 } 845 catch (final Exception e) 846 { 847 Debug.debugException(e); 848 } 849 } 850 851 852 return false; 853 } 854 855 856 857 /** 858 * Retrieves the certificate chain with the specified alias. Note that 859 * because this key manager implementation can only use a single certificate 860 * chain, it will always return the same chain for any alias, even if the 861 * requested alias is {@code null}. 862 * 863 * @param alias The alias for the certificate chain to retrieve. 864 * 865 * @return The certificate chain for this key manager. 866 */ 867 @Override() 868 @NotNull() 869 public X509Certificate[] getCertificateChain(@Nullable final String alias) 870 { 871 return Arrays.copyOf(certificateChain, certificateChain.length); 872 } 873 874 875 876 /** 877 * Retrieves the private key for the certificate chain with the specified 878 * alias. Note that because this key manager implementation can only use a 879 * single certificate chain, it will always return the same private key for 880 * any alias, even if the requested alias is {@code null}. 881 * 882 * @param alias The alias for the private key to retrieve. 883 * 884 * @return The private key for this key manager. 885 */ 886 @Override() 887 @NotNull() 888 public PrivateKey getPrivateKey(@Nullable final String alias) 889 { 890 return privateKey; 891 } 892}