001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-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) 2008-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.FileInputStream; 042import java.io.Serializable; 043import java.security.KeyStore; 044import java.security.KeyStoreException; 045import java.security.cert.Certificate; 046import java.security.cert.X509Certificate; 047import java.util.Date; 048import java.util.Enumeration; 049import javax.net.ssl.KeyManager; 050import javax.net.ssl.KeyManagerFactory; 051import javax.security.auth.x500.X500Principal; 052 053import com.unboundid.util.CryptoHelper; 054import com.unboundid.util.Debug; 055import com.unboundid.util.NotMutable; 056import com.unboundid.util.NotNull; 057import com.unboundid.util.Nullable; 058import com.unboundid.util.StaticUtils; 059import com.unboundid.util.ThreadSafety; 060import com.unboundid.util.ThreadSafetyLevel; 061 062import static com.unboundid.util.ssl.SSLMessages.*; 063 064 065 066/** 067 * This class provides an SSL key manager that may be used to retrieve 068 * certificates from a key store file. By default it will use the default key 069 * store format for the JVM (e.g., "JKS" for Sun-provided Java implementations), 070 * but alternate formats like PKCS12 may be used. 071 */ 072@NotMutable() 073@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 074public final class KeyStoreKeyManager 075 extends WrapperKeyManager 076 implements Serializable 077{ 078 /** 079 * The serial version UID for this serializable class. 080 */ 081 private static final long serialVersionUID = -5202641256733094253L; 082 083 084 085 // The path to the key store file. 086 @NotNull private final String keyStoreFile; 087 088 // The format to use for the key store file. 089 @NotNull private final String keyStoreFormat; 090 091 092 093 /** 094 * Creates a new instance of this key store key manager that provides the 095 * ability to retrieve certificates from the specified key store file. It 096 * will use the default key store format. 097 * 098 * @param keyStoreFile The path to the key store file to use. It must not 099 * be {@code null}. 100 * @param keyStorePIN The PIN to use to access the contents of the key 101 * store. It may be {@code null} if no PIN is required. 102 * 103 * @throws KeyStoreException If a problem occurs while initializing this key 104 * manager. 105 */ 106 public KeyStoreKeyManager(@NotNull final File keyStoreFile, 107 @Nullable final char[] keyStorePIN) 108 throws KeyStoreException 109 { 110 this(keyStoreFile.getAbsolutePath(), keyStorePIN, null, null); 111 } 112 113 114 115 /** 116 * Creates a new instance of this key store key manager that provides the 117 * ability to retrieve certificates from the specified key store file. It 118 * will use the default key store format. 119 * 120 * @param keyStoreFile The path to the key store file to use. It must not 121 * be {@code null}. 122 * @param keyStorePIN The PIN to use to access the contents of the key 123 * store. It may be {@code null} if no PIN is required. 124 * 125 * @throws KeyStoreException If a problem occurs while initializing this key 126 * manager. 127 */ 128 public KeyStoreKeyManager(@NotNull final String keyStoreFile, 129 @Nullable final char[] keyStorePIN) 130 throws KeyStoreException 131 { 132 this(keyStoreFile, keyStorePIN, null, null); 133 } 134 135 136 137 /** 138 * Creates a new instance of this key store key manager that provides the 139 * ability to retrieve certificates from the specified key store file. 140 * 141 * @param keyStoreFile The path to the key store file to use. It must 142 * not be {@code null}. 143 * @param keyStorePIN The PIN to use to access the contents of the key 144 * store. It may be {@code null} if no PIN is 145 * required. 146 * @param keyStoreFormat The format to use for the key store. It may be 147 * {@code null} if the default format should be 148 * used. 149 * @param certificateAlias The nickname of the certificate that should be 150 * selected. It may be {@code null} if any 151 * acceptable certificate found in the keystore may 152 * be used. 153 * 154 * @throws KeyStoreException If a problem occurs while initializing this key 155 * manager. 156 */ 157 public KeyStoreKeyManager(@NotNull final File keyStoreFile, 158 @Nullable final char[] keyStorePIN, 159 @Nullable final String keyStoreFormat, 160 @Nullable final String certificateAlias) 161 throws KeyStoreException 162 { 163 this(keyStoreFile.getAbsolutePath(), keyStorePIN, keyStoreFormat, 164 certificateAlias); 165 } 166 167 168 169 /** 170 * Creates a new instance of this key store key manager that provides the 171 * ability to retrieve certificates from the specified key store file. 172 * 173 * @param keyStoreFile The path to the key store file to use. It must 174 * not be {@code null}. 175 * @param keyStorePIN The PIN to use to access the contents of the key 176 * store. It may be {@code null} if no PIN is 177 * required. 178 * @param keyStoreFormat The format to use for the key store. It may be 179 * {@code null} if the default format should be 180 * used. 181 * @param certificateAlias The nickname of the certificate that should be 182 * selected. It may be {@code null} if any 183 * acceptable certificate found in the keystore may 184 * be used. 185 * 186 * @throws KeyStoreException If a problem occurs while initializing this key 187 * manager. 188 */ 189 public KeyStoreKeyManager(@NotNull final String keyStoreFile, 190 @Nullable final char[] keyStorePIN, 191 @Nullable final String keyStoreFormat, 192 @Nullable final String certificateAlias) 193 throws KeyStoreException 194 { 195 this(keyStoreFile, keyStorePIN, keyStoreFormat, certificateAlias, false); 196 } 197 198 199 200 /** 201 * Creates a new instance of this key store key manager that provides the 202 * ability to retrieve certificates from the specified key store file. 203 * 204 * @param keyStoreFile The path to the key store file to use. It must 205 * not be {@code null}. 206 * @param keyStorePIN The PIN to use to access the contents of the key 207 * store. It may be {@code null} if no PIN is 208 * required. 209 * @param keyStoreFormat The format to use for the key store. It may be 210 * {@code null} if the default format should be 211 * used. 212 * @param certificateAlias The nickname of the certificate that should be 213 * selected. It may be {@code null} if any 214 * acceptable certificate found in the keystore may 215 * be used. 216 * @param validateKeyStore Indicates whether to validate that the provided 217 * key store is acceptable and can actually be used 218 * to obtain a valid certificate. If a certificate 219 * alias was specified, then this will ensure that 220 * the key store contains a valid private key entry 221 * with that alias. If no certificate alias was 222 * specified, then this will ensure that the key 223 * store contains at least one valid private key 224 * entry. 225 * 226 * @throws KeyStoreException If a problem occurs while initializing this key 227 * manager, or if validation fails. 228 */ 229 public KeyStoreKeyManager(@NotNull final File keyStoreFile, 230 @Nullable final char[] keyStorePIN, 231 @Nullable final String keyStoreFormat, 232 @Nullable final String certificateAlias, 233 final boolean validateKeyStore) 234 throws KeyStoreException 235 { 236 this(keyStoreFile.getAbsolutePath(), keyStorePIN, keyStoreFormat, 237 certificateAlias, validateKeyStore); 238 } 239 240 241 242 /** 243 * Creates a new instance of this key store key manager that provides the 244 * ability to retrieve certificates from the specified key store file. 245 * 246 * @param keyStoreFile The path to the key store file to use. It must 247 * not be {@code null}. 248 * @param keyStorePIN The PIN to use to access the contents of the key 249 * store. It may be {@code null} if no PIN is 250 * required. 251 * @param keyStoreFormat The format to use for the key store. It may be 252 * {@code null} if the default format should be 253 * used. 254 * @param certificateAlias The nickname of the certificate that should be 255 * selected. It may be {@code null} if any 256 * acceptable certificate found in the keystore may 257 * be used. 258 * @param validateKeyStore Indicates whether to validate that the provided 259 * key store is acceptable and can actually be used 260 * to obtain a valid certificate. If a certificate 261 * alias was specified, then this will ensure that 262 * the key store contains a valid private key entry 263 * with that alias. If no certificate alias was 264 * specified, then this will ensure that the key 265 * store contains at least one valid private key 266 * entry. 267 * 268 * @throws KeyStoreException If a problem occurs while initializing this key 269 * manager, or if validation fails. 270 */ 271 public KeyStoreKeyManager(@NotNull final String keyStoreFile, 272 @Nullable final char[] keyStorePIN, 273 @Nullable final String keyStoreFormat, 274 @Nullable final String certificateAlias, 275 final boolean validateKeyStore) 276 throws KeyStoreException 277 { 278 this(createProperties(keyStoreFile, keyStorePIN, keyStoreFormat, 279 certificateAlias, validateKeyStore)); 280 } 281 282 283 284 /** 285 * Creates a set of key store key manager properties with the provided 286 * information. 287 * 288 * @param keyStoreFile The path to the key store file to use. It must 289 * not be {@code null}. 290 * @param keyStorePIN The PIN to use to access the contents of the key 291 * store. It may be {@code null} if no PIN is 292 * required. 293 * @param keyStoreFormat The format to use for the key store. It may be 294 * {@code null} if the default format should be 295 * used. 296 * @param certificateAlias The nickname of the certificate that should be 297 * selected. It may be {@code null} if any 298 * acceptable certificate found in the keystore may 299 * be used. 300 * @param validateKeyStore Indicates whether to validate that the provided 301 * key store is acceptable and can actually be used 302 * to obtain a valid certificate. If a certificate 303 * alias was specified, then this will ensure that 304 * the key store contains a valid private key entry 305 * with that alias. If no certificate alias was 306 * specified, then this will ensure that the key 307 * store contains at least one valid private key 308 * entry. 309 * 310 * @return The key store key manager properties object that was created from 311 * the provided information. 312 */ 313 @NotNull() 314 private static KeyStoreKeyManagerProperties createProperties( 315 @NotNull final String keyStoreFile, 316 @Nullable final char[] keyStorePIN, 317 @Nullable final String keyStoreFormat, 318 @Nullable final String certificateAlias, 319 final boolean validateKeyStore) 320 { 321 final KeyStoreKeyManagerProperties properties = 322 new KeyStoreKeyManagerProperties(keyStoreFile); 323 properties.setKeyStorePIN(keyStorePIN); 324 properties.setKeyStoreFormat(keyStoreFormat); 325 properties.setCertificateAlias(certificateAlias); 326 properties.setValidateKeyStore(validateKeyStore); 327 return properties; 328 } 329 330 331 332 /** 333 * Creates a new instance of this key store key manager that provides the 334 * ability to retrieve certificates from the specified key store file. 335 * 336 * @param properties The properties to use to create this key manager. It 337 * must not be {@code null}. 338 * 339 * @throws KeyStoreException If a problem occurs while initializing this key 340 * manager, or if validation fails. 341 */ 342 public KeyStoreKeyManager( 343 @NotNull final KeyStoreKeyManagerProperties properties) 344 throws KeyStoreException 345 { 346 super(getKeyManagers(properties), properties.getCertificateAlias()); 347 348 keyStoreFile = properties.getKeyStorePath(); 349 350 final String keyStoreType = properties.getKeyStoreFormat(); 351 if (keyStoreType == null) 352 { 353 keyStoreFormat = CryptoHelper.getDefaultKeyStoreType(); 354 } 355 else 356 { 357 keyStoreFormat = keyStoreType; 358 } 359 } 360 361 362 363 /** 364 * Retrieves the set of key managers that will be wrapped by this key manager. 365 * 366 * @param properties The properties to use to create the key managers. It 367 * must not be {@code null}. 368 * 369 * @return The set of key managers that will be wrapped by this key manager. 370 * 371 * @throws KeyStoreException If a problem occurs while initializing this key 372 * manager, or if validation fails. 373 */ 374 @NotNull() 375 private static KeyManager[] getKeyManagers( 376 @NotNull final KeyStoreKeyManagerProperties properties) 377 throws KeyStoreException 378 { 379 String type = properties.getKeyStoreFormat(); 380 if (type == null) 381 { 382 type = CryptoHelper.getDefaultKeyStoreType(); 383 } 384 385 final String keyStorePath = properties.getKeyStorePath(); 386 final File f = new File(keyStorePath); 387 if (! f.exists()) 388 { 389 throw new KeyStoreException(ERR_KEYSTORE_NO_SUCH_FILE.get(keyStorePath)); 390 } 391 392 final char[] keyStorePIN = properties.getKeyStorePIN(); 393 final KeyStore ks = CryptoHelper.getKeyStore(type, 394 properties.getProvider(), properties.allowNonFIPSInFIPSMode()); 395 FileInputStream inputStream = null; 396 try 397 { 398 inputStream = new FileInputStream(f); 399 ks.load(inputStream, keyStorePIN); 400 } 401 catch (final Exception e) 402 { 403 Debug.debugException(e); 404 405 throw new KeyStoreException( 406 ERR_KEYSTORE_CANNOT_LOAD.get(keyStorePath, type, String.valueOf(e)), 407 e); 408 } 409 finally 410 { 411 if (inputStream != null) 412 { 413 try 414 { 415 inputStream.close(); 416 } 417 catch (final Exception e) 418 { 419 Debug.debugException(e); 420 } 421 } 422 } 423 424 if (properties.validateKeyStore()) 425 { 426 validateKeyStore(ks, f, keyStorePIN, properties.getCertificateAlias()); 427 } 428 429 try 430 { 431 final KeyManagerFactory factory = CryptoHelper.getKeyManagerFactory(); 432 factory.init(ks, keyStorePIN); 433 return factory.getKeyManagers(); 434 } 435 catch (final Exception e) 436 { 437 Debug.debugException(e); 438 439 throw new KeyStoreException( 440 ERR_KEYSTORE_CANNOT_GET_KEY_MANAGERS.get(keyStorePath, type, 441 StaticUtils.getExceptionMessage(e)), 442 e); 443 } 444 } 445 446 447 448 /** 449 * Validates that the provided key store has an appropriate private key entry 450 * in which all certificates in the chain are currently within the validity 451 * window. 452 * 453 * @param keyStore The key store to examine. It must not be 454 * {@code null}. 455 * @param keyStoreFile The file that backs the key store. It must not 456 * be {@code null}. 457 * @param keyStorePIN The PIN to use to access the contents of the key 458 * store. It may be {@code null} if no PIN is 459 * required. 460 * @param certificateAlias The nickname of the certificate that should be 461 * selected. It may be {@code null} if any 462 * acceptable certificate found in the keystore may 463 * be used. 464 * 465 * @throws KeyStoreException If a validation error was encountered. 466 */ 467 private static void validateKeyStore(@NotNull final KeyStore keyStore, 468 @NotNull final File keyStoreFile, 469 @Nullable final char[] keyStorePIN, 470 @Nullable final String certificateAlias) 471 throws KeyStoreException 472 { 473 final KeyStore.ProtectionParameter protectionParameter; 474 if (keyStorePIN == null) 475 { 476 protectionParameter = null; 477 } 478 else 479 { 480 protectionParameter = new KeyStore.PasswordProtection(keyStorePIN); 481 } 482 483 try 484 { 485 if (certificateAlias == null) 486 { 487 final StringBuilder invalidMessages = new StringBuilder(); 488 final Enumeration<String> aliases = keyStore.aliases(); 489 while (aliases.hasMoreElements()) 490 { 491 final String alias = aliases.nextElement(); 492 if (! keyStore.isKeyEntry(alias)) 493 { 494 continue; 495 } 496 497 try 498 { 499 final KeyStore.PrivateKeyEntry entry = 500 (KeyStore.PrivateKeyEntry) 501 keyStore.getEntry(alias, protectionParameter); 502 ensureAllCertificatesInChainAreValid(alias, entry); 503 504 // We found a private key entry in which all certificates in the 505 // chain are within their validity window, so we'll assume that 506 // it's acceptable. 507 return; 508 } 509 catch (final Exception e) 510 { 511 Debug.debugException(e); 512 if (invalidMessages.length() > 0) 513 { 514 invalidMessages.append(" "); 515 } 516 invalidMessages.append(e.getMessage()); 517 } 518 } 519 520 if ( invalidMessages.length() > 0) 521 { 522 // The key store has at least one private key entry, but none of 523 // them are currently valid. 524 throw new KeyStoreException( 525 ERR_KEYSTORE_NO_VALID_PRIVATE_KEY_ENTRIES.get( 526 keyStoreFile.getAbsolutePath(), 527 invalidMessages.toString())); 528 } 529 else 530 { 531 // The key store doesn't have any private key entries. 532 throw new KeyStoreException(ERR_KEYSTORE_NO_PRIVATE_KEY_ENTRIES.get( 533 keyStoreFile.getAbsolutePath())); 534 } 535 } 536 else 537 { 538 if (! keyStore.containsAlias(certificateAlias)) 539 { 540 throw new KeyStoreException(ERR_KEYSTORE_NO_ENTRY_WITH_ALIAS.get( 541 keyStoreFile.getAbsolutePath(), certificateAlias)); 542 } 543 544 if (! keyStore.isKeyEntry(certificateAlias)) 545 { 546 throw new KeyStoreException(ERR_KEYSTORE_ENTRY_NOT_PRIVATE_KEY.get( 547 certificateAlias, keyStoreFile.getAbsolutePath())); 548 } 549 550 final KeyStore.PrivateKeyEntry entry = 551 (KeyStore.PrivateKeyEntry) 552 keyStore.getEntry(certificateAlias, protectionParameter); 553 ensureAllCertificatesInChainAreValid(certificateAlias, entry); 554 } 555 } 556 catch (final KeyStoreException e) 557 { 558 Debug.debugException(e); 559 throw e; 560 } 561 catch (final Exception e) 562 { 563 Debug.debugException(e); 564 throw new KeyStoreException( 565 ERR_KEYSTORE_CANNOT_VALIDATE.get(keyStoreFile.getAbsolutePath(), 566 StaticUtils.getExceptionMessage(e)), 567 e); 568 } 569 } 570 571 572 573 /** 574 * Ensures that all certificates in the provided private key entry's chain are 575 * currently within their validity window. 576 * 577 * @param alias The alias from which the entry was read. It must not be 578 * {@code null}. 579 * @param entry The private key entry to examine. It must not be 580 * {@code null}. 581 * 582 * @throws KeyStoreException If any certificate in the chain is expired or 583 * not yet valid. 584 */ 585 private static void ensureAllCertificatesInChainAreValid( 586 @NotNull final String alias, 587 @NotNull final KeyStore.PrivateKeyEntry entry) 588 throws KeyStoreException 589 { 590 final Date currentTime = new Date(); 591 for (final Certificate cert : entry.getCertificateChain()) 592 { 593 if (cert instanceof X509Certificate) 594 { 595 final X509Certificate c = (X509Certificate) cert; 596 if (currentTime.before(c.getNotBefore())) 597 { 598 throw new KeyStoreException( 599 ERR_KEYSTORE_CERT_NOT_YET_VALID.get(alias, 600 c.getSubjectX500Principal().getName( 601 X500Principal.RFC2253), 602 String.valueOf(c.getNotBefore()))); 603 } 604 else if (currentTime.after(c.getNotAfter())) 605 { 606 throw new KeyStoreException( 607 ERR_KEYSTORE_CERT_EXPIRED.get(alias, 608 c.getSubjectX500Principal().getName( 609 X500Principal.RFC2253), 610 String.valueOf(c.getNotAfter()))); 611 } 612 } 613 } 614 } 615 616 617 618 /** 619 * Retrieves the path to the key store file to use. 620 * 621 * @return The path to the key store file to use. 622 */ 623 @NotNull() 624 public String getKeyStoreFile() 625 { 626 return keyStoreFile; 627 } 628 629 630 631 /** 632 * Retrieves the name of the key store file format. 633 * 634 * @return The name of the key store file format. 635 */ 636 @NotNull() 637 public String getKeyStoreFormat() 638 { 639 return keyStoreFormat; 640 } 641}