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 }