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