View Javadoc
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   */
18  
19  package org.apache.commons.net.util;
20  
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.IOException;
24  import java.net.Socket;
25  import java.security.GeneralSecurityException;
26  import java.security.KeyStore;
27  import java.security.KeyStoreException;
28  import java.security.Principal;
29  import java.security.PrivateKey;
30  import java.security.cert.Certificate;
31  import java.security.cert.X509Certificate;
32  import java.util.Enumeration;
33  
34  import javax.net.ssl.KeyManager;
35  import javax.net.ssl.X509ExtendedKeyManager;
36  
37  import org.apache.commons.net.io.Util;
38  
39  /**
40   * General KeyManager utilities
41   * <p>
42   * How to use with a client certificate:
43   * <pre>
44   * KeyManager km = KeyManagerUtils.createClientKeyManager("JKS",
45   *     "/path/to/privatekeystore.jks","storepassword",
46   *     "privatekeyalias", "keypassword");
47   * FTPSClient cl = new FTPSClient();
48   * cl.setKeyManager(km);
49   * cl.connect(...);
50   * </pre>
51   * If using the default store type and the key password is the same as the
52   * store password, these parameters can be omitted. <br>
53   * If the desired key is the first or only key in the keystore, the keyAlias parameter
54   * can be omitted, in which case the code becomes:
55   * <pre>
56   * KeyManager km = KeyManagerUtils.createClientKeyManager(
57   *     "/path/to/privatekeystore.jks","storepassword");
58   * FTPSClient cl = new FTPSClient();
59   * cl.setKeyManager(km);
60   * cl.connect(...);
61   * </pre>
62   *
63   * @since 3.0
64   */
65  public final class KeyManagerUtils {
66  
67      private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType();
68  
69      private KeyManagerUtils(){
70          // Not instantiable
71      }
72  
73      /**
74       * Create a client key manager which returns a particular key.
75       * Does not handle server keys.
76       *
77       * @param ks the keystore to use
78       * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
79       * @param keyPass the password of the key to use
80       * @return the customised KeyManager
81       * @throws GeneralSecurityException if there is a problem creating the keystore
82       */
83      public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass)
84          throws GeneralSecurityException
85      {
86          ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass);
87          return new X509KeyManager(cks);
88      }
89  
90      /**
91       * Create a client key manager which returns a particular key.
92       * Does not handle server keys.
93       *
94       * @param storeType the type of the keyStore, e.g. "JKS"
95       * @param storePath the path to the keyStore
96       * @param storePass the keyStore password
97       * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
98       * @param keyPass the password of the key to use
99       * @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 }