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.Arrays; 033import java.util.Enumeration; 034 035import javax.net.ssl.KeyManager; 036import javax.net.ssl.X509ExtendedKeyManager; 037 038import org.apache.commons.net.io.Util; 039 040/** 041 * General KeyManager utilities 042 * <p> 043 * How to use with a client certificate: 044 * 045 * <pre> 046 * KeyManager km = KeyManagerUtils.createClientKeyManager("JKS", 047 * "/path/to/privatekeystore.jks","storepassword", 048 * "privatekeyalias", "keypassword"); 049 * FTPSClient cl = new FTPSClient(); 050 * cl.setKeyManager(km); 051 * cl.connect(...); 052 * </pre> 053 * 054 * If using the default store type and the key password is the same as the store password, these parameters can be omitted. <br> 055 * If the desired key is the first or only key in the keystore, the keyAlias parameter can be omitted, in which case the code becomes: 056 * 057 * <pre> 058 * KeyManager km = KeyManagerUtils.createClientKeyManager( 059 * "/path/to/privatekeystore.jks","storepassword"); 060 * FTPSClient cl = new FTPSClient(); 061 * cl.setKeyManager(km); 062 * cl.connect(...); 063 * </pre> 064 * 065 * @since 3.0 066 */ 067public final class KeyManagerUtils { 068 069 private static class ClientKeyStore { 070 071 private final X509Certificate[] certChain; 072 private final PrivateKey key; 073 private final String keyAlias; 074 075 ClientKeyStore(final KeyStore ks, final String keyAlias, final String keyPass) throws GeneralSecurityException { 076 this.keyAlias = keyAlias; 077 this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray()); 078 final Certificate[] certs = ks.getCertificateChain(this.keyAlias); 079 final X509Certificate[] x509certs = new X509Certificate[certs.length]; 080 Arrays.setAll(x509certs, i -> (X509Certificate) certs[i]); 081 this.certChain = x509certs; 082 } 083 084 final String getAlias() { 085 return this.keyAlias; 086 } 087 088 final X509Certificate[] getCertificateChain() { 089 return this.certChain; 090 } 091 092 final PrivateKey getPrivateKey() { 093 return this.key; 094 } 095 } 096 097 private static class X509KeyManager extends X509ExtendedKeyManager { 098 099 private final ClientKeyStore keyStore; 100 101 X509KeyManager(final ClientKeyStore keyStore) { 102 this.keyStore = keyStore; 103 } 104 105 // Call sequence: 1 106 @Override 107 public String chooseClientAlias(final String[] keyType, final Principal[] issuers, final Socket socket) { 108 return keyStore.getAlias(); 109 } 110 111 @Override 112 public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) { 113 return null; 114 } 115 116 // Call sequence: 2 117 @Override 118 public X509Certificate[] getCertificateChain(final String alias) { 119 return keyStore.getCertificateChain(); 120 } 121 122 @Override 123 public String[] getClientAliases(final String keyType, final Principal[] issuers) { 124 return new String[] { keyStore.getAlias() }; 125 } 126 127 // Call sequence: 3 128 @Override 129 public PrivateKey getPrivateKey(final String alias) { 130 return keyStore.getPrivateKey(); 131 } 132 133 @Override 134 public String[] getServerAliases(final String keyType, final Principal[] issuers) { 135 return null; 136 } 137 138 } 139 140 private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType(); 141 142 /** 143 * Create a client key manager which returns a particular key. Does not handle server keys. Uses the default store type and assumes the key password is the 144 * same as the store password. The key alias is found by searching the keystore for the first private key entry 145 * 146 * @param storePath the path to the keyStore 147 * @param storePass the keyStore password 148 * @return the customised KeyManager 149 * @throws IOException if there is a problem creating the keystore 150 * @throws GeneralSecurityException if there is a problem creating the keystore 151 */ 152 public static KeyManager createClientKeyManager(final File storePath, final String storePass) throws IOException, GeneralSecurityException { 153 return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass); 154 } 155 156 /** 157 * Create a client key manager which returns a particular key. Does not handle server keys. Uses the default store type and assumes the key password is the 158 * same as the store password 159 * 160 * @param storePath the path to the keyStore 161 * @param storePass the keyStore password 162 * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used 163 * @return the customised KeyManager 164 * @throws IOException if there is a problem creating the keystore 165 * @throws GeneralSecurityException if there is a problem creating the keystore 166 */ 167 public static KeyManager createClientKeyManager(final File storePath, final String storePass, final String keyAlias) 168 throws IOException, GeneralSecurityException { 169 return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass); 170 } 171 172 /** 173 * Create a client key manager which returns a particular key. Does not handle server keys. 174 * 175 * @param ks the keystore to use 176 * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used 177 * @param keyPass the password of the key to use 178 * @return the customised KeyManager 179 * @throws GeneralSecurityException if there is a problem creating the keystore 180 */ 181 public static KeyManager createClientKeyManager(final KeyStore ks, final String keyAlias, final String keyPass) throws GeneralSecurityException { 182 final ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass); 183 return new X509KeyManager(cks); 184 } 185 186 /** 187 * Create a client key manager which returns a particular key. Does not handle server keys. 188 * 189 * @param storeType the type of the keyStore, e.g. "JKS" 190 * @param storePath the path to the keyStore 191 * @param storePass the keyStore password 192 * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used 193 * @param keyPass the password of the key to use 194 * @return the customised KeyManager 195 * @throws GeneralSecurityException if there is a problem creating the keystore 196 * @throws IOException if there is a problem creating the keystore 197 */ 198 public static KeyManager createClientKeyManager(final String storeType, final File storePath, final String storePass, final String keyAlias, 199 final String keyPass) throws IOException, GeneralSecurityException { 200 final KeyStore ks = loadStore(storeType, storePath, storePass); 201 return createClientKeyManager(ks, keyAlias, keyPass); 202 } 203 204 private static String findAlias(final KeyStore ks) throws KeyStoreException { 205 final Enumeration<String> e = ks.aliases(); 206 while (e.hasMoreElements()) { 207 final String entry = e.nextElement(); 208 if (ks.isKeyEntry(entry)) { 209 return entry; 210 } 211 } 212 throw new KeyStoreException("Cannot find a private key entry"); 213 } 214 215 private static KeyStore loadStore(final String storeType, final File storePath, final String storePass) 216 throws KeyStoreException, IOException, GeneralSecurityException { 217 final KeyStore ks = KeyStore.getInstance(storeType); 218 FileInputStream stream = null; 219 try { 220 stream = new FileInputStream(storePath); 221 ks.load(stream, storePass.toCharArray()); 222 } finally { 223 Util.closeQuietly(stream); 224 } 225 return ks; 226 } 227 228 private KeyManagerUtils() { 229 // Not instantiable 230 } 231 232}