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 }