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