    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *   http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     *
    017     */
    018
    019    package org.apache.commons.net.util;
    020
    021    import java.io.File;
    022    import java.io.FileInputStream;
    023    import java.io.IOException;
    024    import java.net.Socket;
    025    import java.security.GeneralSecurityException;
    026    import java.security.KeyStore;
    027    import java.security.KeyStoreException;
    028    import java.security.Principal;
    029    import java.security.PrivateKey;
    030    import java.security.cert.Certificate;
    031    import java.security.cert.X509Certificate;
    032    import java.util.Enumeration;
    033
    034    import javax.net.ssl.KeyManager;
    035    import javax.net.ssl.X509ExtendedKeyManager;
    036
    037    import org.apache.commons.net.io.Util;
    038
    039    /**
    040     * General KeyManager utilities
    041     * <p>
    042     * How to use with a client certificate:
    043     * <pre>
    044     * KeyManager km = KeyManagerUtils.createClientKeyManager("JKS",
    045     *     "/path/to/privatekeystore.jks","storepassword",
    046     *     "privatekeyalias", "keypassword");
    047     * FTPSClient cl = new FTPSClient();
    048     * cl.setKeyManager(km);
    049     * cl.connect(...);
    050     * </pre>
    051     * If using the default store type and the key password is the same as the
    052     * store password, these parameters can be omitted. <br/>
    053     * If the desired key is the first or only key in the keystore, the keyAlias parameter
    054     * can be omitted, in which case the code becomes:
    055     * <pre>
    056     * KeyManager km = KeyManagerUtils.createClientKeyManager(
    057     *     "/path/to/privatekeystore.jks","storepassword");
    058     * FTPSClient cl = new FTPSClient();
    059     * cl.setKeyManager(km);
    060     * cl.connect(...);
    061     * </pre>
    062     *
    063     * @since 3.0
    064     */
    065    public final class KeyManagerUtils {
    066
    067        private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType();
    068
    069        private KeyManagerUtils(){
    070            // Not instantiable
    071        }
    072
    073        /**
    074         * Create a client key manager which returns a particular key.
    075         * Does not handle server keys.
    076         *
    077         * @param ks the keystore to use
    078         * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
    079         * @param keyPass the password of the key to use
    080         * @return the customised KeyManager
    081         */
    082        public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass)
    083            throws GeneralSecurityException
    084        {
    085            ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass);
    086            return new X509KeyManager(cks);
    087        }
    088
    089        /**
    090         * Create a client key manager which returns a particular key.
    091         * Does not handle server keys.
    092         *
    093         * @param storeType the type of the keyStore, e.g. "JKS"
    094         * @param storePath the path to the keyStore
    095         * @param storePass the keyStore password
    096         * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
    097         * @param keyPass the password of the key to use
    098         * @return the customised KeyManager
    099         */
    100        public static KeyManager createClientKeyManager(String storeType, File storePath, String storePass, String keyAlias, String keyPass)
    101            throws IOException, GeneralSecurityException
    102        {
    103            KeyStore ks = loadStore(storeType, storePath, storePass);
    104            return createClientKeyManager(ks, keyAlias, keyPass);
    105        }
    106
    107        /**
    108         * Create a client key manager which returns a particular key.
    109         * Does not handle server keys.
    110         * Uses the default store type and assumes the key password is the same as the store password
    111         *
    112         * @param storePath the path to the keyStore
    113         * @param storePass the keyStore password
    114         * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
    115         * @return the customised KeyManager
    116         */
    117        public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias)
    118            throws IOException, GeneralSecurityException
    119        {
    120            return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass);
    121        }
    122
    123        /**
    124         * Create a client key manager which returns a particular key.
    125         * Does not handle server keys.
    126         * Uses the default store type and assumes the key password is the same as the store password.
    127         * The key alias is found by searching the keystore for the first private key entry
    128         *
    129         * @param storePath the path to the keyStore
    130         * @param storePass the keyStore password
    131         * @return the customised KeyManager
    132         */
    133        public static KeyManager createClientKeyManager(File storePath, String storePass)
    134            throws IOException, GeneralSecurityException
    135        {
    136            return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass);
    137        }
    138
    139        private static KeyStore loadStore(String storeType, File storePath, String storePass)
    140            throws KeyStoreException,  IOException, GeneralSecurityException {
    141            KeyStore ks = KeyStore.getInstance(storeType);
    142            FileInputStream stream = null;
    143            try {
    144                stream = new FileInputStream(storePath);
    145                ks.load(stream, storePass.toCharArray());
    146            } finally {
    147                Util.closeQuietly(stream);
    148            }
    149            return ks;
    150        }
    151
    152        private static String findAlias(KeyStore ks) throws KeyStoreException {
    153            Enumeration<String> e = ks.aliases();
    154            while(e.hasMoreElements()) {
    155                String entry = e.nextElement();
    156                if (ks.isKeyEntry(entry)) {
    157                    return entry;
    158                }
    159            }
    160            throw new KeyStoreException("Cannot find a private key entry");
    161        }
    162
    163        private static class ClientKeyStore {
    164
    165            private final X509Certificate[] certChain;
    166            private final PrivateKey key;
    167            private final String keyAlias;
    168
    169            ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException
    170            {
    171                this.keyAlias = keyAlias;
    172                this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray());
    173                Certificate[] certs = ks.getCertificateChain(this.keyAlias);
    174                X509Certificate[] X509certs = new X509Certificate[certs.length];
    175                for (int i=0; i < certs.length; i++) {
    176                    X509certs[i] = (X509Certificate) certs[i];
    177                }
    178                this.certChain = X509certs;
    179            }
    180
    181            final X509Certificate[] getCertificateChain() {
    182                return this.certChain;
    183            }
    184
    185            final PrivateKey getPrivateKey() {
    186                return this.key;
    187            }
    188
    189            final String getAlias() {
    190                return this.keyAlias;
    191            }
    192        }
    193
    194        private static class X509KeyManager extends X509ExtendedKeyManager  {
    195
    196            private final ClientKeyStore keyStore;
    197
    198            X509KeyManager(final ClientKeyStore keyStore) {
    199                this.keyStore = keyStore;
    200            }
    201
    202            // Call sequence: 1
    203            public String chooseClientAlias(String[] keyType, Principal[] issuers,
    204                    Socket socket) {
    205                return keyStore.getAlias();
    206            }
    207
    208            // Call sequence: 2
    209            public X509Certificate[] getCertificateChain(String alias) {
    210                return keyStore.getCertificateChain();
    211            }
    212
    213            public String[] getClientAliases(String keyType, Principal[] issuers) {
    214                return new String[]{ keyStore.getAlias()};
    215            }
    216
    217            // Call sequence: 3
    218            public PrivateKey getPrivateKey(String alias) {
    219                return keyStore.getPrivateKey();
    220            }
    221
    222            public String[] getServerAliases(String keyType, Principal[] issuers) {
    223                return null;
    224            }
    225
    226            public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
    227                return null;
    228            }
    229
    230        }
    231
    232    }