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}