001/* 002 * Copyright 2008-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2020 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-2020 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.Debug; 054import com.unboundid.util.NotMutable; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.Nullable; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.Validator; 061 062import static com.unboundid.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 super( 279 getKeyManagers(keyStoreFile, keyStorePIN, keyStoreFormat, 280 certificateAlias, validateKeyStore), 281 certificateAlias); 282 283 this.keyStoreFile = keyStoreFile; 284 285 if (keyStoreFormat == null) 286 { 287 this.keyStoreFormat = KeyStore.getDefaultType(); 288 } 289 else 290 { 291 this.keyStoreFormat = keyStoreFormat; 292 } 293 } 294 295 296 297 /** 298 * Retrieves the set of key managers that will be wrapped by this key manager. 299 * 300 * @param keyStoreFile The path to the key store file to use. It must 301 * not be {@code null}. 302 * @param keyStorePIN The PIN to use to access the contents of the key 303 * store. It may be {@code null} if no PIN is 304 * required. 305 * @param keyStoreFormat The format to use for the key store. It may be 306 * {@code null} if the default format should be 307 * used. 308 * @param certificateAlias The nickname of the certificate that should be 309 * selected. It may be {@code null} if any 310 * acceptable certificate found in the keystore may 311 * be used. 312 * @param validateKeyStore Indicates whether to validate that the provided 313 * key store is acceptable and can actually be used 314 * to obtain a valid certificate. If a certificate 315 * alias was specified, then this will ensure that 316 * the key store contains a valid private key entry 317 * with that alias. If no certificate alias was 318 * specified, then this will ensure that the key 319 * store contains at least one valid private key 320 * entry. 321 * 322 * @return The set of key managers that will be wrapped by this key manager. 323 * 324 * @throws KeyStoreException If a problem occurs while initializing this key 325 * manager, or if validation fails. 326 */ 327 @NotNull() 328 private static KeyManager[] getKeyManagers( 329 @NotNull final String keyStoreFile, 330 @Nullable final char[] keyStorePIN, 331 @Nullable final String keyStoreFormat, 332 @Nullable final String certificateAlias, 333 final boolean validateKeyStore) 334 throws KeyStoreException 335 { 336 Validator.ensureNotNull(keyStoreFile); 337 338 String type = keyStoreFormat; 339 if (type == null) 340 { 341 type = KeyStore.getDefaultType(); 342 } 343 344 final File f = new File(keyStoreFile); 345 if (! f.exists()) 346 { 347 throw new KeyStoreException(ERR_KEYSTORE_NO_SUCH_FILE.get(keyStoreFile)); 348 } 349 350 final KeyStore ks = KeyStore.getInstance(type); 351 FileInputStream inputStream = null; 352 try 353 { 354 inputStream = new FileInputStream(f); 355 ks.load(inputStream, keyStorePIN); 356 } 357 catch (final Exception e) 358 { 359 Debug.debugException(e); 360 361 throw new KeyStoreException( 362 ERR_KEYSTORE_CANNOT_LOAD.get(keyStoreFile, type, String.valueOf(e)), 363 e); 364 } 365 finally 366 { 367 if (inputStream != null) 368 { 369 try 370 { 371 inputStream.close(); 372 } 373 catch (final Exception e) 374 { 375 Debug.debugException(e); 376 } 377 } 378 } 379 380 if (validateKeyStore) 381 { 382 validateKeyStore(ks, f, keyStorePIN, certificateAlias); 383 } 384 385 try 386 { 387 final KeyManagerFactory factory = KeyManagerFactory.getInstance( 388 KeyManagerFactory.getDefaultAlgorithm()); 389 factory.init(ks, keyStorePIN); 390 return factory.getKeyManagers(); 391 } 392 catch (final Exception e) 393 { 394 Debug.debugException(e); 395 396 throw new KeyStoreException( 397 ERR_KEYSTORE_CANNOT_GET_KEY_MANAGERS.get(keyStoreFile, 398 keyStoreFormat, StaticUtils.getExceptionMessage(e)), 399 e); 400 } 401 } 402 403 404 405 /** 406 * Validates that the provided key store has an appropriate private key entry 407 * in which all certificates in the chain are currently within the validity 408 * window. 409 * 410 * @param keyStore The key store to examine. It must not be 411 * {@code null}. 412 * @param keyStoreFile The file that backs the key store. It must not 413 * be {@code null}. 414 * @param keyStorePIN The PIN to use to access the contents of the key 415 * store. It may be {@code null} if no PIN is 416 * required. 417 * @param certificateAlias The nickname of the certificate that should be 418 * selected. It may be {@code null} if any 419 * acceptable certificate found in the keystore may 420 * be used. 421 * 422 * @throws KeyStoreException If a validation error was encountered. 423 */ 424 private static void validateKeyStore(@NotNull final KeyStore keyStore, 425 @NotNull final File keyStoreFile, 426 @Nullable final char[] keyStorePIN, 427 @Nullable final String certificateAlias) 428 throws KeyStoreException 429 { 430 final KeyStore.ProtectionParameter protectionParameter; 431 if (keyStorePIN == null) 432 { 433 protectionParameter = null; 434 } 435 else 436 { 437 protectionParameter = new KeyStore.PasswordProtection(keyStorePIN); 438 } 439 440 try 441 { 442 if (certificateAlias == null) 443 { 444 final StringBuilder invalidMessages = new StringBuilder(); 445 final Enumeration<String> aliases = keyStore.aliases(); 446 while (aliases.hasMoreElements()) 447 { 448 final String alias = aliases.nextElement(); 449 if (! keyStore.isKeyEntry(alias)) 450 { 451 continue; 452 } 453 454 try 455 { 456 final KeyStore.PrivateKeyEntry entry = 457 (KeyStore.PrivateKeyEntry) 458 keyStore.getEntry(alias, protectionParameter); 459 ensureAllCertificatesInChainAreValid(alias, entry); 460 461 // We found a private key entry in which all certificates in the 462 // chain are within their validity window, so we'll assume that 463 // it's acceptable. 464 return; 465 } 466 catch (final Exception e) 467 { 468 Debug.debugException(e); 469 if (invalidMessages.length() > 0) 470 { 471 invalidMessages.append(" "); 472 } 473 invalidMessages.append(e.getMessage()); 474 } 475 } 476 477 if ( invalidMessages.length() > 0) 478 { 479 // The key store has at least one private key entry, but none of 480 // them are currently valid. 481 throw new KeyStoreException( 482 ERR_KEYSTORE_NO_VALID_PRIVATE_KEY_ENTRIES.get( 483 keyStoreFile.getAbsolutePath(), 484 invalidMessages.toString())); 485 } 486 else 487 { 488 // The key store doesn't have any private key entries. 489 throw new KeyStoreException(ERR_KEYSTORE_NO_PRIVATE_KEY_ENTRIES.get( 490 keyStoreFile.getAbsolutePath())); 491 } 492 } 493 else 494 { 495 if (! keyStore.containsAlias(certificateAlias)) 496 { 497 throw new KeyStoreException(ERR_KEYSTORE_NO_ENTRY_WITH_ALIAS.get( 498 keyStoreFile.getAbsolutePath(), certificateAlias)); 499 } 500 501 if (! keyStore.isKeyEntry(certificateAlias)) 502 { 503 throw new KeyStoreException(ERR_KEYSTORE_ENTRY_NOT_PRIVATE_KEY.get( 504 certificateAlias, keyStoreFile.getAbsolutePath())); 505 } 506 507 final KeyStore.PrivateKeyEntry entry = 508 (KeyStore.PrivateKeyEntry) 509 keyStore.getEntry(certificateAlias, protectionParameter); 510 ensureAllCertificatesInChainAreValid(certificateAlias, entry); 511 } 512 } 513 catch (final KeyStoreException e) 514 { 515 Debug.debugException(e); 516 throw e; 517 } 518 catch (final Exception e) 519 { 520 Debug.debugException(e); 521 throw new KeyStoreException( 522 ERR_KEYSTORE_CANNOT_VALIDATE.get(keyStoreFile.getAbsolutePath(), 523 StaticUtils.getExceptionMessage(e)), 524 e); 525 } 526 } 527 528 529 530 /** 531 * Ensures that all certificates in the provided private key entry's chain are 532 * currently within their validity window. 533 * 534 * @param alias The alias from which the entry was read. It must not be 535 * {@code null}. 536 * @param entry The private key entry to examine. It must not be 537 * {@code null}. 538 * 539 * @throws KeyStoreException If any certificate in the chain is expired or 540 * not yet valid. 541 */ 542 private static void ensureAllCertificatesInChainAreValid( 543 @NotNull final String alias, 544 @NotNull final KeyStore.PrivateKeyEntry entry) 545 throws KeyStoreException 546 { 547 final Date currentTime = new Date(); 548 for (final Certificate cert : entry.getCertificateChain()) 549 { 550 if (cert instanceof X509Certificate) 551 { 552 final X509Certificate c = (X509Certificate) cert; 553 if (currentTime.before(c.getNotBefore())) 554 { 555 throw new KeyStoreException( 556 ERR_KEYSTORE_CERT_NOT_YET_VALID.get(alias, 557 c.getSubjectX500Principal().getName( 558 X500Principal.RFC2253), 559 String.valueOf(c.getNotBefore()))); 560 } 561 else if (currentTime.after(c.getNotAfter())) 562 { 563 throw new KeyStoreException( 564 ERR_KEYSTORE_CERT_EXPIRED.get(alias, 565 c.getSubjectX500Principal().getName( 566 X500Principal.RFC2253), 567 String.valueOf(c.getNotAfter()))); 568 } 569 } 570 } 571 } 572 573 574 575 /** 576 * Retrieves the path to the key store file to use. 577 * 578 * @return The path to the key store file to use. 579 */ 580 @NotNull() 581 public String getKeyStoreFile() 582 { 583 return keyStoreFile; 584 } 585 586 587 588 /** 589 * Retrieves the name of the key store file format. 590 * 591 * @return The name of the key store file format. 592 */ 593 @NotNull() 594 public String getKeyStoreFormat() 595 { 596 return keyStoreFormat; 597 } 598}