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.  *   http://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.net.Socket;
  22. import java.security.GeneralSecurityException;
  23. import java.security.KeyStore;
  24. import java.security.KeyStoreException;
  25. import java.security.Principal;
  26. import java.security.PrivateKey;
  27. import java.security.cert.Certificate;
  28. import java.security.cert.X509Certificate;
  29. import java.util.Arrays;
  30. import java.util.Enumeration;

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

  33. import org.apache.commons.net.io.Util;

  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.  *
  48.  * If using the default store type and the key password is the same as the store password, these parameters can be omitted. <br>
  49.  * 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:
  50.  *
  51.  * <pre>
  52.  * KeyManager km = KeyManagerUtils.createClientKeyManager(
  53.  *     "/path/to/privatekeystore.jks","storepassword");
  54.  * FTPSClient cl = new FTPSClient();
  55.  * cl.setKeyManager(km);
  56.  * cl.connect(...);
  57.  * </pre>
  58.  *
  59.  * @since 3.0
  60.  */
  61. public final class KeyManagerUtils {

  62.     private static final class ClientKeyStore {

  63.         private final X509Certificate[] certChain;
  64.         private final PrivateKey key;
  65.         private final String keyAlias;

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

  74.         String getAlias() {
  75.             return this.keyAlias;
  76.         }

  77.         X509Certificate[] getCertificateChain() {
  78.             return this.certChain;
  79.         }

  80.         PrivateKey getPrivateKey() {
  81.             return this.key;
  82.         }
  83.     }

  84.     private static final class X509KeyManager extends X509ExtendedKeyManager {

  85.         private final ClientKeyStore keyStore;

  86.         X509KeyManager(final ClientKeyStore keyStore) {
  87.             this.keyStore = keyStore;
  88.         }

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

  94.         @Override
  95.         public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) {
  96.             return null;
  97.         }

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

  103.         @Override
  104.         public String[] getClientAliases(final String keyType, final Principal[] issuers) {
  105.             return new String[] { keyStore.getAlias() };
  106.         }

  107.         // Call sequence: 3
  108.         @Override
  109.         public PrivateKey getPrivateKey(final String alias) {
  110.             return keyStore.getPrivateKey();
  111.         }

  112.         @Override
  113.         public String[] getServerAliases(final String keyType, final Principal[] issuers) {
  114.             return null;
  115.         }

  116.     }

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

  118.     /**
  119.      * 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
  120.      * same as the store password. The key alias is found by searching the keystore for the first private key entry
  121.      *
  122.      * @param storePath the path to the keyStore
  123.      * @param storePass the keyStore password
  124.      * @return the customised KeyManager
  125.      * @throws IOException              if there is a problem creating the keystore
  126.      * @throws GeneralSecurityException if there is a problem creating the keystore
  127.      */
  128.     public static KeyManager createClientKeyManager(final File storePath, final String storePass) throws IOException, GeneralSecurityException {
  129.         return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass);
  130.     }

  131.     /**
  132.      * 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
  133.      * same as the store password
  134.      *
  135.      * @param storePath the path to the keyStore
  136.      * @param storePass the keyStore password
  137.      * @param keyAlias  the alias of the key to use, may be {@code null} in which case the first key entry alias is used
  138.      * @return the customised KeyManager
  139.      * @throws IOException              if there is a problem creating the keystore
  140.      * @throws GeneralSecurityException if there is a problem creating the keystore
  141.      */
  142.     public static KeyManager createClientKeyManager(final File storePath, final String storePass, final String keyAlias)
  143.             throws IOException, GeneralSecurityException {
  144.         return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass);
  145.     }

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

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

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

  186.     private static KeyStore loadStore(final String storeType, final File storePath, final String storePass)
  187.             throws KeyStoreException, IOException, GeneralSecurityException {
  188.         final KeyStore ks = KeyStore.getInstance(storeType);
  189.         FileInputStream stream = null;
  190.         try {
  191.             stream = new FileInputStream(storePath);
  192.             ks.load(stream, storePass.toCharArray());
  193.         } finally {
  194.             Util.closeQuietly(stream);
  195.         }
  196.         return ks;
  197.     }

  198.     private KeyManagerUtils() {
  199.         // Not instantiable
  200.     }

  201. }