All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.security.keystore.KeyStoreHelper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.security.keystore;

import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStoreException;
import java.security.Provider;
import java.security.UnrecoverableKeyException;
import java.util.Enumeration;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.PresentForCodeCoverage;
import com.helger.commons.concurrent.SimpleReadWriteLock;
import com.helger.commons.functional.IThrowingConsumer;
import com.helger.commons.io.resourceprovider.ClassPathResourceProvider;
import com.helger.commons.io.resourceprovider.FileSystemResourceProvider;
import com.helger.commons.io.resourceprovider.IReadableResourceProvider;
import com.helger.commons.io.resourceprovider.ReadableResourceProviderChain;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.lang.ClassHelper;
import com.helger.commons.string.StringHelper;

/**
 * Helper methods to access Java key stores of type JKS (Java KeyStore).
 *
 * @author PEPPOL.AT, BRZ, Philip Helger
 */
@ThreadSafe
public final class KeyStoreHelper
{
  private static final Logger LOGGER = LoggerFactory.getLogger (KeyStoreHelper.class);
  private static final SimpleReadWriteLock RW_LOCK = new SimpleReadWriteLock ();
  @GuardedBy ("RW_LOCK")
  private static IReadableResourceProvider s_aResourceProvider = new ReadableResourceProviderChain (new FileSystemResourceProvider ().setCanReadRelativePaths (true),
                                                                                                    new ClassPathResourceProvider ());

  @PresentForCodeCoverage
  private static final KeyStoreHelper INSTANCE = new KeyStoreHelper ();

  private KeyStoreHelper ()
  {}

  @Nonnull
  public static IReadableResourceProvider getResourceProvider ()
  {
    return RW_LOCK.readLockedGet ( () -> s_aResourceProvider);
  }

  public static void setResourceProvider (@Nonnull final IReadableResourceProvider aResourceProvider)
  {
    ValueEnforcer.notNull (aResourceProvider, "ResourceProvider");
    RW_LOCK.writeLocked ( () -> s_aResourceProvider = aResourceProvider);
  }

  @Nonnull
  public static KeyStore getSimiliarKeyStore (@Nonnull final KeyStore aOther) throws KeyStoreException
  {
    ValueEnforcer.notNull (aOther, "Other");

    return getSimiliarKeyStore (aOther, null);
  }

  @Nonnull
  public static KeyStore getSimiliarKeyStore (@Nonnull final KeyStore aOther,
                                              @Nullable final Provider aSecurityProvider) throws KeyStoreException
  {
    ValueEnforcer.notNull (aOther, "Other");

    return KeyStore.getInstance (aOther.getType (),
                                 aSecurityProvider != null ? aSecurityProvider : aOther.getProvider ());
  }

  /**
   * Load a key store from a resource.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        The path pointing to the key store. May only be null
   *        for {@link EKeyStoreType#PKCS11}.
   * @param sKeyStorePassword
   *        The key store password. May be null to indicate that no
   *        password is required.
   * @return The Java key-store object.
   * @throws GeneralSecurityException
   *         In case of a key store error
   * @throws IOException
   *         In case key store loading fails
   * @throws IllegalArgumentException
   *         If the key store path is invalid
   * @deprecated Use the version with char[] as password type
   */
  @Nonnull
  @Deprecated (forRemoval = true, since = "11.1.9")
  public static KeyStore loadKeyStoreDirect (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final String sKeyStorePassword) throws GeneralSecurityException,
                                                                                       IOException
  {
    return loadKeyStoreDirect (aKeyStoreType, sKeyStorePath, sKeyStorePassword, null);
  }

  /**
   * Load a key store from a resource.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        The path pointing to the key store. May only be null
   *        for {@link EKeyStoreType#PKCS11}.
   * @param sKeyStorePassword
   *        The key store password. May be null to indicate that no
   *        password is required.
   * @param aSecurityProvider
   *        The Security Provider to use. May be null.
   * @return The Java key-store object.
   * @throws GeneralSecurityException
   *         In case of a key store error
   * @throws IOException
   *         In case key store loading fails
   * @throws IllegalArgumentException
   *         If the key store path is invalid
   * @since 11.1.1
   * @deprecated Use the version with char[] as password type
   */
  @Nonnull
  @Deprecated (forRemoval = true, since = "11.1.9")
  public static KeyStore loadKeyStoreDirect (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final String sKeyStorePassword,
                                             @Nullable final Provider aSecurityProvider) throws GeneralSecurityException,
                                                                                         IOException
  {
    return loadKeyStoreDirect (aKeyStoreType,
                               sKeyStorePath,
                               sKeyStorePassword == null ? null : sKeyStorePassword.toCharArray (),
                               aSecurityProvider);
  }

  /**
   * Load a key store from a resource.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        The path pointing to the key store. May only be null
   *        for {@link EKeyStoreType#PKCS11} or other key store types that don't
   *        require a path.
   * @param aKeyStorePassword
   *        The key store password. May be null to indicate that no
   *        password is required.
   * @return The Java key-store object.
   * @see KeyStore#load(InputStream, char[])
   * @throws GeneralSecurityException
   *         In case of a key store error
   * @throws IOException
   *         In case key store loading fails
   * @throws IllegalArgumentException
   *         If the key store path is invalid
   */
  @Nonnull
  public static KeyStore loadKeyStoreDirect (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final char [] aKeyStorePassword) throws GeneralSecurityException,
                                                                                        IOException
  {
    return loadKeyStoreDirect (aKeyStoreType, sKeyStorePath, aKeyStorePassword, null);
  }

  /**
   * Load a key store from a resource.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        The path pointing to the key store. May only be null
   *        for {@link EKeyStoreType#PKCS11} or other key store types that don't
   *        require a path.
   * @param aKeyStorePassword
   *        The key store password. May be null to indicate that no
   *        password is required.
   * @param aSecurityProvider
   *        The Security Provider to use. May be null.
   * @return The Java key-store object.
   * @see KeyStore#load(InputStream, char[])
   * @throws GeneralSecurityException
   *         In case of a key store error
   * @throws IOException
   *         In case key store loading fails
   * @throws IllegalArgumentException
   *         If the key store path is invalid
   * @since 11.1.1
   */
  @Nonnull
  public static KeyStore loadKeyStoreDirect (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final char [] aKeyStorePassword,
                                             @Nullable final Provider aSecurityProvider) throws GeneralSecurityException,
                                                                                         IOException
  {
    ValueEnforcer.notNull (aKeyStoreType, "KeyStoreType");

    final InputStream aIS;
    if (aKeyStoreType.isKeyStorePathRequired ())
    {
      ValueEnforcer.notNull (sKeyStorePath, "KeyStorePath");

      // Open the resource stream
      aIS = getResourceProvider ().getInputStream (sKeyStorePath);
      if (aIS == null)
        throw new IllegalArgumentException ("Failed to open key store '" + sKeyStorePath + "'");
    }
    else
    {
      aIS = null;
    }
    try
    {
      if (LOGGER.isDebugEnabled ())
        LOGGER.debug ("Trying to load key store from path '" +
                      sKeyStorePath +
                      "' using type " +
                      aKeyStoreType.getID ());

      final KeyStore aKeyStore = aSecurityProvider != null ? aKeyStoreType.getKeyStore (aSecurityProvider)
                                                           : aKeyStoreType.getKeyStore ();
      aKeyStore.load (aIS, aKeyStorePassword);
      return aKeyStore;
    }
    catch (final KeyStoreException ex)
    {
      throw new IllegalStateException ("No provider can handle key stores of type " + aKeyStoreType, ex);
    }
    finally
    {
      StreamHelper.close (aIS);
    }
  }

  /**
   * Create a new key store based on an existing key store
   *
   * @param aBaseKeyStore
   *        The source key store. May not be null
   * @param sAliasToCopy
   *        The name of the alias in the source key store that should be put in
   *        the new key store
   * @param aAliasPassword
   *        The optional password to access the alias in the source key store.
   *        If it is not null the same password will be used in the
   *        created key store
   * @return The created in-memory key store
   * @throws GeneralSecurityException
   *         In case of a key store error
   * @throws IOException
   *         In case key store loading fails
   */
  @Nonnull
  public static KeyStore createKeyStoreWithOnlyOneItem (@Nonnull final KeyStore aBaseKeyStore,
                                                        @Nonnull final String sAliasToCopy,
                                                        @Nullable final char [] aAliasPassword) throws GeneralSecurityException,
                                                                                                IOException
  {
    return createKeyStoreWithOnlyOneItem (aBaseKeyStore, sAliasToCopy, aAliasPassword, null);
  }

  /**
   * Create a new key store based on an existing key store
   *
   * @param aBaseKeyStore
   *        The source key store. May not be null
   * @param sAliasToCopy
   *        The name of the alias in the source key store that should be put in
   *        the new key store
   * @param aAliasPassword
   *        The optional password to access the alias in the source key store.
   *        If it is not null the same password will be used in the
   *        created key store
   * @param aSecurityProvider
   *        The Security Provider to use. May be null.
   * @return The created in-memory key store
   * @throws GeneralSecurityException
   *         In case of a key store error
   * @throws IOException
   *         In case key store loading fails
   * @since 11.1.1
   */
  @Nonnull
  public static KeyStore createKeyStoreWithOnlyOneItem (@Nonnull final KeyStore aBaseKeyStore,
                                                        @Nonnull final String sAliasToCopy,
                                                        @Nullable final char [] aAliasPassword,
                                                        @Nullable final Provider aSecurityProvider) throws GeneralSecurityException,
                                                                                                    IOException
  {
    ValueEnforcer.notNull (aBaseKeyStore, "BaseKeyStore");
    ValueEnforcer.notNull (sAliasToCopy, "AliasToCopy");

    if (LOGGER.isDebugEnabled ())
      LOGGER.debug ("Create a new key store using type " + aBaseKeyStore.getType ());

    final KeyStore aKeyStore = getSimiliarKeyStore (aBaseKeyStore, aSecurityProvider);
    // null stream means: create new key store
    aKeyStore.load (null, null);

    // Do we need a password?
    ProtectionParameter aPP = null;
    if (aAliasPassword != null)
      aPP = new PasswordProtection (aAliasPassword);

    if (LOGGER.isDebugEnabled ())
      LOGGER.debug ("Copying alias '" + sAliasToCopy + "' from old key store to new key store");

    aKeyStore.setEntry (sAliasToCopy, aBaseKeyStore.getEntry (sAliasToCopy, aPP), aPP);
    return aKeyStore;
  }

  private static boolean _isInvalidPasswordException (@Nonnull final Exception ex)
  {
    return ex instanceof IOException && ex.getCause () instanceof UnrecoverableKeyException;
  }

  /**
   * Load the provided key store in a safe manner.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        Path to the key store. May not be null for all key
   *        store types that require a path.
   * @param sKeyStorePassword
   *        Password for the key store. May not be null to succeed.
   * @return The key store loading result. Never null.
   * @deprecated Use the version with char[] as password type
   */
  @Nonnull
  @Deprecated (forRemoval = true, since = "11.1.9")
  public static LoadedKeyStore loadKeyStore (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final String sKeyStorePassword)
  {
    return loadKeyStore (aKeyStoreType, sKeyStorePath, sKeyStorePassword, null);
  }

  /**
   * Load the provided key store in a safe manner.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        Path to the key store. May not be null for all key
   *        store types that require a path.
   * @param sKeyStorePassword
   *        Password for the key store. May not be null to succeed.
   * @param aSecurityProvider
   *        The Security Provider to use. May be null.
   * @return The key store loading result. Never null.
   * @since 11.1.1
   * @deprecated Use the version with char[] as password type
   */
  @Nonnull
  @Deprecated (forRemoval = true, since = "11.1.9")
  public static LoadedKeyStore loadKeyStore (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final String sKeyStorePassword,
                                             @Nullable final Provider aSecurityProvider)
  {
    return loadKeyStore (aKeyStoreType,
                         sKeyStorePath,
                         sKeyStorePassword == null ? null : sKeyStorePassword.toCharArray (),
                         null);
  }

  /**
   * Load the provided key store in a safe manner.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        Path to the key store. May not be null for all key
   *        store types that require a path.
   * @param aKeyStorePassword
   *        Password for the key store. May not be null to succeed.
   * @return The key store loading result. Never null.
   * @since 11.1.9
   */
  @Nonnull
  public static LoadedKeyStore loadKeyStore (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final char [] aKeyStorePassword)
  {
    return loadKeyStore (aKeyStoreType, sKeyStorePath, aKeyStorePassword, null);
  }

  /**
   * Load the provided key store in a safe manner.
   *
   * @param aKeyStoreType
   *        Type of key store. May not be null.
   * @param sKeyStorePath
   *        Path to the key store. May not be null for all key
   *        store types that require a path.
   * @param aKeyStorePassword
   *        Password for the key store. May not be null to succeed.
   * @param aSecurityProvider
   *        The Security Provider to use. May be null.
   * @return The key store loading result. Never null.
   * @since 11.1.9
   */
  @Nonnull
  public static LoadedKeyStore loadKeyStore (@Nonnull final IKeyStoreType aKeyStoreType,
                                             @Nullable final String sKeyStorePath,
                                             @Nullable final char [] aKeyStorePassword,
                                             @Nullable final Provider aSecurityProvider)
  {
    ValueEnforcer.notNull (aKeyStoreType, "KeyStoreType");

    // Get the parameters for the key store
    if (aKeyStoreType.isKeyStorePathRequired () && StringHelper.hasNoText (sKeyStorePath))
      return new LoadedKeyStore (null, EKeyStoreLoadError.KEYSTORE_NO_PATH);

    final KeyStore aKeyStore;
    // Try to load key store
    try
    {
      aKeyStore = loadKeyStoreDirect (aKeyStoreType, sKeyStorePath, aKeyStorePassword, aSecurityProvider);
    }
    catch (final IllegalArgumentException ex)
    {
      LOGGER.warn ("No such key store '" + sKeyStorePath + "': " + ex.getMessage (), ex.getCause ());

      return new LoadedKeyStore (null,
                                 EKeyStoreLoadError.KEYSTORE_LOAD_ERROR_NON_EXISTING,
                                 sKeyStorePath,
                                 ex.getMessage ());
    }
    catch (final Exception ex)
    {
      final boolean bInvalidPW = _isInvalidPasswordException (ex);

      LOGGER.warn ("Failed to load key store '" +
                   sKeyStorePath +
                   "' of type " +
                   aKeyStoreType.getID () +
                   ": " +
                   ex.getMessage (),
                   bInvalidPW ? null : ex.getCause ());

      return new LoadedKeyStore (null,
                                 bInvalidPW ? EKeyStoreLoadError.KEYSTORE_INVALID_PASSWORD
                                            : EKeyStoreLoadError.KEYSTORE_LOAD_ERROR_FORMAT_ERROR,
                                 sKeyStorePath,
                                 ex.getMessage ());
    }

    // Finally success
    return new LoadedKeyStore (aKeyStore, null);
  }

  @Nonnull
  private static  LoadedKey  _loadKey (@Nonnull final KeyStore aKeyStore,
                                                                    @Nonnull final String sKeyStorePath,
                                                                    @Nullable final String sKeyStoreKeyAlias,
                                                                    @Nullable final char [] aKeyStoreKeyPassword,
                                                                    @Nonnull final Class  aTargetClass)
  {
    ValueEnforcer.notNull (aKeyStore, "KeyStore");
    ValueEnforcer.notNull (sKeyStorePath, "KeyStorePath");
    ValueEnforcer.notNull (aTargetClass, "TargetClass");

    if (StringHelper.hasNoText (sKeyStoreKeyAlias))
      return new LoadedKey <> (null, EKeyStoreLoadError.KEY_NO_ALIAS, sKeyStorePath);

    if (aKeyStoreKeyPassword == null)
      return new LoadedKey <> (null, EKeyStoreLoadError.KEY_NO_PASSWORD, sKeyStoreKeyAlias, sKeyStorePath);

    // Try to load the key.
    final T aKeyEntry;
    try
    {
      if (LOGGER.isDebugEnabled ())
        LOGGER.debug ("Trying to access key store entry with alias '" +
                      sKeyStoreKeyAlias +
                      "' as a " +
                      aTargetClass.getName ());

      final KeyStore.ProtectionParameter aProtection = new KeyStore.PasswordProtection (aKeyStoreKeyPassword);
      final KeyStore.Entry aEntry = aKeyStore.getEntry (sKeyStoreKeyAlias, aProtection);
      if (aEntry == null)
      {
        // No such entry
        return new LoadedKey <> (null, EKeyStoreLoadError.KEY_INVALID_ALIAS, sKeyStoreKeyAlias, sKeyStorePath);
      }
      if (!aTargetClass.isAssignableFrom (aEntry.getClass ()))
      {
        // Not a matching
        return new LoadedKey <> (null,
                                 EKeyStoreLoadError.KEY_INVALID_TYPE,
                                 sKeyStoreKeyAlias,
                                 sKeyStorePath,
                                 ClassHelper.getClassName (aEntry));
      }
      aKeyEntry = aTargetClass.cast (aEntry);
    }
    catch (final UnrecoverableKeyException ex)
    {
      return new LoadedKey <> (null,
                               EKeyStoreLoadError.KEY_INVALID_PASSWORD,
                               sKeyStoreKeyAlias,
                               sKeyStorePath,
                               ex.getMessage ());
    }
    catch (final GeneralSecurityException ex)
    {
      return new LoadedKey <> (null,
                               EKeyStoreLoadError.KEY_LOAD_ERROR,
                               sKeyStoreKeyAlias,
                               sKeyStorePath,
                               ex.getMessage ());
    }

    // Finally success
    return new LoadedKey <> (aKeyEntry, null);
  }

  /**
   * Load the specified private key entry from the provided key store.
   *
   * @param aKeyStore
   *        The key store to load the key from. May not be null.
   * @param sKeyStorePath
   *        Key store path. For nice error messages only. May not be
   *        null.
   * @param sKeyStoreKeyAlias
   *        The alias to be resolved in the key store. Must be non-
   *        null to succeed.
   * @param aKeyStoreKeyPassword
   *        The key password for the key store. Must be non-null to
   *        succeed.
   * @return The key loading result. Never null.
   */
  @Nonnull
  public static LoadedKey  loadPrivateKey (@Nonnull final KeyStore aKeyStore,
                                                                     @Nonnull final String sKeyStorePath,
                                                                     @Nullable final String sKeyStoreKeyAlias,
                                                                     @Nullable final char [] aKeyStoreKeyPassword)
  {
    return _loadKey (aKeyStore, sKeyStorePath, sKeyStoreKeyAlias, aKeyStoreKeyPassword, KeyStore.PrivateKeyEntry.class);
  }

  /**
   * Load the specified secret key entry from the provided key store.
   *
   * @param aKeyStore
   *        The key store to load the key from. May not be null.
   * @param sKeyStorePath
   *        Key store path. For nice error messages only. May not be
   *        null.
   * @param sKeyStoreKeyAlias
   *        The alias to be resolved in the key store. Must be non-
   *        null to succeed.
   * @param aKeyStoreKeyPassword
   *        The key password for the key store. Must be non-null to
   *        succeed.
   * @return The key loading result. Never null.
   */
  @Nonnull
  public static LoadedKey  loadSecretKey (@Nonnull final KeyStore aKeyStore,
                                                                   @Nonnull final String sKeyStorePath,
                                                                   @Nullable final String sKeyStoreKeyAlias,
                                                                   @Nullable final char [] aKeyStoreKeyPassword)
  {
    return _loadKey (aKeyStore, sKeyStorePath, sKeyStoreKeyAlias, aKeyStoreKeyPassword, KeyStore.SecretKeyEntry.class);
  }

  /**
   * Load the specified private key entry from the provided key store.
   *
   * @param aKeyStore
   *        The key store to load the key from. May not be null.
   * @param sKeyStorePath
   *        Key store path. For nice error messages only. May not be
   *        null.
   * @param sKeyStoreKeyAlias
   *        The alias to be resolved in the key store. Must be non-
   *        null to succeed.
   * @param aKeyStoreKeyPassword
   *        The key password for the key store. Must be non-null to
   *        succeed.
   * @return The key loading result. Never null.
   */
  @Nonnull
  public static LoadedKey  loadTrustedCertificateKey (@Nonnull final KeyStore aKeyStore,
                                                                                        @Nonnull final String sKeyStorePath,
                                                                                        @Nullable final String sKeyStoreKeyAlias,
                                                                                        @Nullable final char [] aKeyStoreKeyPassword)
  {
    return _loadKey (aKeyStore,
                     sKeyStorePath,
                     sKeyStoreKeyAlias,
                     aKeyStoreKeyPassword,
                     KeyStore.TrustedCertificateEntry.class);
  }

  /**
   * Helper method to iterate all aliases of a key store
   *
   * @param aKeyStore
   *        The key store to be iterated. May not be null.
   * @param aAliasConsumer
   *        The consumer for each alias. May not be null.
   * @since 11.1.10
   */
  public static void iterateKeyStore (@Nonnull final KeyStore aKeyStore,
                                      @Nonnull final IThrowingConsumer  aAliasConsumer)
  {
    ValueEnforcer.notNull (aKeyStore, "KeyStore");
    ValueEnforcer.notNull (aAliasConsumer, "AliasConsumer");

    try
    {
      final Enumeration  aAliases = aKeyStore.aliases ();
      while (aAliases.hasMoreElements ())
      {
        final String sAlias = aAliases.nextElement ();
        aAliasConsumer.accept (sAlias);
      }
    }
    catch (final KeyStoreException ex)
    {
      LOGGER.warn ("Failed to iterate key store", ex);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy