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
019package org.apache.commons.net.util;
020
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.IOException;
024import java.net.Socket;
025import java.security.GeneralSecurityException;
026import java.security.KeyStore;
027import java.security.KeyStoreException;
028import java.security.Principal;
029import java.security.PrivateKey;
030import java.security.cert.Certificate;
031import java.security.cert.X509Certificate;
032import java.util.Enumeration;
033
034import javax.net.ssl.KeyManager;
035import javax.net.ssl.X509ExtendedKeyManager;
036
037import 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 */
065public 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     * @throws GeneralSecurityException if there is a problem creating the keystore
082     */
083    public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass)
084        throws GeneralSecurityException
085    {
086        ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass);
087        return new X509KeyManager(cks);
088    }
089
090    /**
091     * Create a client key manager which returns a particular key.
092     * Does not handle server keys.
093     *
094     * @param storeType the type of the keyStore, e.g. "JKS"
095     * @param storePath the path to the keyStore
096     * @param storePass the keyStore password
097     * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
098     * @param keyPass the password of the key to use
099     * @return the customised KeyManager
100     * @throws GeneralSecurityException if there is a problem creating the keystore
101     * @throws IOException if there is a problem creating the keystore
102     */
103    public static KeyManager createClientKeyManager(
104            String storeType, File storePath, String storePass, String keyAlias, String keyPass)
105        throws IOException, GeneralSecurityException
106    {
107        KeyStore ks = loadStore(storeType, storePath, storePass);
108        return createClientKeyManager(ks, keyAlias, keyPass);
109    }
110
111    /**
112     * Create a client key manager which returns a particular key.
113     * Does not handle server keys.
114     * Uses the default store type and assumes the key password is the same as the store password
115     *
116     * @param storePath the path to the keyStore
117     * @param storePass the keyStore password
118     * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
119     * @return the customised KeyManager
120     * @throws IOException if there is a problem creating the keystore
121     * @throws GeneralSecurityException if there is a problem creating the keystore
122     */
123    public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias)
124        throws IOException, GeneralSecurityException
125    {
126        return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass);
127    }
128
129    /**
130     * Create a client key manager which returns a particular key.
131     * Does not handle server keys.
132     * Uses the default store type and assumes the key password is the same as the store password.
133     * The key alias is found by searching the keystore for the first private key entry
134     *
135     * @param storePath the path to the keyStore
136     * @param storePass the keyStore password
137     * @return the customised KeyManager
138     * @throws IOException if there is a problem creating the keystore
139     * @throws GeneralSecurityException if there is a problem creating the keystore
140     */
141    public static KeyManager createClientKeyManager(File storePath, String storePass)
142        throws IOException, GeneralSecurityException
143    {
144        return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass);
145    }
146
147    private static KeyStore loadStore(String storeType, File storePath, String storePass)
148        throws KeyStoreException,  IOException, GeneralSecurityException {
149        KeyStore ks = KeyStore.getInstance(storeType);
150        FileInputStream stream = null;
151        try {
152            stream = new FileInputStream(storePath);
153            ks.load(stream, storePass.toCharArray());
154        } finally {
155            Util.closeQuietly(stream);
156        }
157        return ks;
158    }
159
160    private static String findAlias(KeyStore ks) throws KeyStoreException {
161        Enumeration<String> e = ks.aliases();
162        while(e.hasMoreElements()) {
163            String entry = e.nextElement();
164            if (ks.isKeyEntry(entry)) {
165                return entry;
166            }
167        }
168        throw new KeyStoreException("Cannot find a private key entry");
169    }
170
171    private static class ClientKeyStore {
172
173        private final X509Certificate[] certChain;
174        private final PrivateKey key;
175        private final String keyAlias;
176
177        ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException
178        {
179            this.keyAlias = keyAlias;
180            this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray());
181            Certificate[] certs = ks.getCertificateChain(this.keyAlias);
182            X509Certificate[] X509certs = new X509Certificate[certs.length];
183            for (int i=0; i < certs.length; i++) {
184                X509certs[i] = (X509Certificate) certs[i];
185            }
186            this.certChain = X509certs;
187        }
188
189        final X509Certificate[] getCertificateChain() {
190            return this.certChain;
191        }
192
193        final PrivateKey getPrivateKey() {
194            return this.key;
195        }
196
197        final String getAlias() {
198            return this.keyAlias;
199        }
200    }
201
202    private static class X509KeyManager extends X509ExtendedKeyManager  {
203
204        private final ClientKeyStore keyStore;
205
206        X509KeyManager(final ClientKeyStore keyStore) {
207            this.keyStore = keyStore;
208        }
209
210        // Call sequence: 1
211        @Override
212        public String chooseClientAlias(String[] keyType, Principal[] issuers,
213                Socket socket) {
214            return keyStore.getAlias();
215        }
216
217        // Call sequence: 2
218        @Override
219        public X509Certificate[] getCertificateChain(String alias) {
220            return keyStore.getCertificateChain();
221        }
222
223        @Override
224        public String[] getClientAliases(String keyType, Principal[] issuers) {
225            return new String[]{ keyStore.getAlias()};
226        }
227
228        // Call sequence: 3
229        @Override
230        public PrivateKey getPrivateKey(String alias) {
231            return keyStore.getPrivateKey();
232        }
233
234        @Override
235        public String[] getServerAliases(String keyType, Principal[] issuers) {
236            return null;
237        }
238
239        @Override
240        public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
241            return null;
242        }
243
244    }
245
246}