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