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}