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    }