KeyManagerUtils.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.util;

  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.net.Socket;
  23. import java.security.GeneralSecurityException;
  24. import java.security.KeyStore;
  25. import java.security.KeyStoreException;
  26. import java.security.Principal;
  27. import java.security.PrivateKey;
  28. import java.security.cert.Certificate;
  29. import java.security.cert.X509Certificate;
  30. import java.util.Arrays;
  31. import java.util.Enumeration;

  32. import javax.net.ssl.KeyManager;
  33. import javax.net.ssl.X509ExtendedKeyManager;

  34. /**
  35.  * General KeyManager utilities
  36.  * <p>
  37.  * How to use with a client certificate:
  38.  *
  39.  * <pre>
  40.  * KeyManager km = KeyManagerUtils.createClientKeyManager("JKS",
  41.  *     "/path/to/privatekeystore.jks","storepassword",
  42.  *     "privatekeyalias", "keypassword");
  43.  * FTPSClient cl = new FTPSClient();
  44.  * cl.setKeyManager(km);
  45.  * cl.connect(...);
  46.  * </pre>
  47.  * <p>
  48.  * If using the default store type and the key password is the same as the store password, these parameters can be omitted.
  49.  * </p>
  50.  * <p>
  51.  * If the desired key is the first or only key in the keystore, the keyAlias parameter can be omitted, in which case the code becomes:
  52.  * </p>
  53.  *
  54.  * <pre>
  55.  * KeyManager km = KeyManagerUtils.createClientKeyManager(
  56.  *     "/path/to/privatekeystore.jks","storepassword");
  57.  * FTPSClient cl = new FTPSClient();
  58.  * cl.setKeyManager(km);
  59.  * cl.connect(...);
  60.  * </pre>
  61.  *
  62.  * @since 3.0
  63.  */
  64. public final class KeyManagerUtils {

  65.     private static final class ClientKeyStore {

  66.         private final X509Certificate[] certChain;
  67.         private final PrivateKey key;
  68.         private final String keyAlias;

  69.         ClientKeyStore(final KeyStore ks, final String keyAlias, final String keyPass) throws GeneralSecurityException {
  70.             this.keyAlias = keyAlias;
  71.             this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray());
  72.             final Certificate[] certs = ks.getCertificateChain(this.keyAlias);
  73.             final X509Certificate[] x509certs = new X509Certificate[certs.length];
  74.             Arrays.setAll(x509certs, i -> (X509Certificate) certs[i]);
  75.             this.certChain = x509certs;
  76.         }

  77.         String getAlias() {
  78.             return keyAlias;
  79.         }

  80.         X509Certificate[] getCertificateChain() {
  81.             return certChain;
  82.         }

  83.         PrivateKey getPrivateKey() {
  84.             return key;
  85.         }
  86.     }

  87.     private static final class X509KeyManager extends X509ExtendedKeyManager {

  88.         private final ClientKeyStore keyStore;

  89.         X509KeyManager(final ClientKeyStore keyStore) {
  90.             this.keyStore = keyStore;
  91.         }

  92.         // Call sequence: 1
  93.         @Override
  94.         public String chooseClientAlias(final String[] keyType, final Principal[] issuers, final Socket socket) {
  95.             return keyStore.getAlias();
  96.         }

  97.         @Override
  98.         public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) {
  99.             return null;
  100.         }

  101.         // Call sequence: 2
  102.         @Override
  103.         public X509Certificate[] getCertificateChain(final String alias) {
  104.             return keyStore.getCertificateChain();
  105.         }

  106.         @Override
  107.         public String[] getClientAliases(final String keyType, final Principal[] issuers) {
  108.             return new String[] { keyStore.getAlias()};
  109.         }

  110.         // Call sequence: 3
  111.         @Override
  112.         public PrivateKey getPrivateKey(final String alias) {
  113.             return keyStore.getPrivateKey();
  114.         }

  115.         @Override
  116.         public String[] getServerAliases(final String keyType, final Principal[] issuers) {
  117.             return null;
  118.         }

  119.     }

  120.     private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType();

  121.     /**
  122.      * Create a client key manager which returns a particular key. Does not handle server keys. Uses the default store type and assumes the key password is the
  123.      * same as the store password. The key alias is found by searching the keystore for the first private key entry
  124.      *
  125.      * @param storePath the path to the keyStore
  126.      * @param storePass the keyStore password
  127.      * @return the customized KeyManager
  128.      * @throws IOException              if there is a problem creating the keystore
  129.      * @throws GeneralSecurityException if there is a problem creating the keystore
  130.      */
  131.     public static KeyManager createClientKeyManager(final File storePath, final String storePass) throws IOException, GeneralSecurityException {
  132.         return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass);
  133.     }

  134.     /**
  135.      * Create a client key manager which returns a particular key. Does not handle server keys. Uses the default store type and assumes the key password is the
  136.      * same as the store password
  137.      *
  138.      * @param storePath the path to the keyStore
  139.      * @param storePass the keyStore password
  140.      * @param keyAlias  the alias of the key to use, may be {@code null} in which case the first key entry alias is used
  141.      * @return the customized KeyManager
  142.      * @throws IOException              if there is a problem creating the keystore
  143.      * @throws GeneralSecurityException if there is a problem creating the keystore
  144.      */
  145.     public static KeyManager createClientKeyManager(final File storePath, final String storePass, final String keyAlias)
  146.             throws IOException, GeneralSecurityException {
  147.         return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass);
  148.     }

  149.     /**
  150.      * Create a client key manager which returns a particular key. Does not handle server keys.
  151.      *
  152.      * @param ks       the keystore to use
  153.      * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
  154.      * @param keyPass  the password of the key to use
  155.      * @return the customized KeyManager
  156.      * @throws GeneralSecurityException if there is a problem creating the keystore
  157.      */
  158.     public static KeyManager createClientKeyManager(final KeyStore ks, final String keyAlias, final String keyPass) throws GeneralSecurityException {
  159.         final ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass);
  160.         return new X509KeyManager(cks);
  161.     }

  162.     /**
  163.      * Create a client key manager which returns a particular key. Does not handle server keys.
  164.      *
  165.      * @param storeType the type of the keyStore, e.g. "JKS"
  166.      * @param storePath the path to the keyStore
  167.      * @param storePass the keyStore password
  168.      * @param keyAlias  the alias of the key to use, may be {@code null} in which case the first key entry alias is used
  169.      * @param keyPass   the password of the key to use
  170.      * @return the customized KeyManager
  171.      * @throws GeneralSecurityException if there is a problem creating the keystore
  172.      * @throws IOException              if there is a problem creating the keystore
  173.      */
  174.     public static KeyManager createClientKeyManager(final String storeType, final File storePath, final String storePass, final String keyAlias,
  175.             final String keyPass) throws IOException, GeneralSecurityException {
  176.         return createClientKeyManager(loadStore(storeType, storePath, storePass), keyAlias, keyPass);
  177.     }

  178.     private static String findAlias(final KeyStore ks) throws KeyStoreException {
  179.         final Enumeration<String> e = ks.aliases();
  180.         while (e.hasMoreElements()) {
  181.             final String entry = e.nextElement();
  182.             if (ks.isKeyEntry(entry)) {
  183.                 return entry;
  184.             }
  185.         }
  186.         throw new KeyStoreException("Cannot find a private key entry");
  187.     }

  188.     private static KeyStore loadStore(final String storeType, final File storePath, final String storePass) throws IOException, GeneralSecurityException {
  189.         final KeyStore ks = KeyStore.getInstance(storeType);
  190.         try (InputStream stream = new FileInputStream(storePath)) {
  191.             ks.load(stream, storePass.toCharArray());
  192.         }
  193.         return ks;
  194.     }

  195.     private KeyManagerUtils() {
  196.         // Not instantiable
  197.     }

  198. }