blob: cc940721601455b18360987e359ca90c0790df84 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.commons.net.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import javax.net.ssl.KeyManager;
import javax.net.ssl.X509ExtendedKeyManager;
import org.apache.commons.net.io.Util;
/**
* General KeyManager utilities
* <p>
* How to use with a client certificate:
* <pre>
* KeyManager km = KeyManagerUtils.createClientKeyManager("JKS",
* "/path/to/privatekeystore.jks","storepassword",
* "privatekeyalias", "keypassword");
* FTPSClient cl = new FTPSClient();
* cl.setKeyManager(km);
* cl.connect(...);
* </pre>
* If using the default store type and the key password is the same as the
* store password, these parameters can be omitted. <br>
* 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:
* <pre>
* KeyManager km = KeyManagerUtils.createClientKeyManager(
* "/path/to/privatekeystore.jks","storepassword");
* FTPSClient cl = new FTPSClient();
* cl.setKeyManager(km);
* cl.connect(...);
* </pre>
*
* @since 3.0
*/
public final class KeyManagerUtils {
private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType();
private KeyManagerUtils(){
// Not instantiable
}
/**
* Create a client key manager which returns a particular key.
* Does not handle server keys.
*
* @param ks the keystore to use
* @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
* @param keyPass the password of the key to use
* @return the customised KeyManager
* @throws GeneralSecurityException if there is a problem creating the keystore
*/
public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass)
throws GeneralSecurityException
{
ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass);
return new X509KeyManager(cks);
}
/**
* Create a client key manager which returns a particular key.
* Does not handle server keys.
*
* @param storeType the type of the keyStore, e.g. "JKS"
* @param storePath the path to the keyStore
* @param storePass the keyStore password
* @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
* @param keyPass the password of the key to use
* @return the customised KeyManager
* @throws GeneralSecurityException if there is a problem creating the keystore
* @throws IOException if there is a problem creating the keystore
*/
public static KeyManager createClientKeyManager(
String storeType, File storePath, String storePass, String keyAlias, String keyPass)
throws IOException, GeneralSecurityException
{
KeyStore ks = loadStore(storeType, storePath, storePass);
return createClientKeyManager(ks, keyAlias, keyPass);
}
/**
* 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 same as the store password
*
* @param storePath the path to the keyStore
* @param storePass the keyStore password
* @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used
* @return the customised KeyManager
* @throws IOException if there is a problem creating the keystore
* @throws GeneralSecurityException if there is a problem creating the keystore
*/
public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias)
throws IOException, GeneralSecurityException
{
return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass);
}
/**
* 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 same as the store password.
* The key alias is found by searching the keystore for the first private key entry
*
* @param storePath the path to the keyStore
* @param storePass the keyStore password
* @return the customised KeyManager
* @throws IOException if there is a problem creating the keystore
* @throws GeneralSecurityException if there is a problem creating the keystore
*/
public static KeyManager createClientKeyManager(File storePath, String storePass)
throws IOException, GeneralSecurityException
{
return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass);
}
private static KeyStore loadStore(String storeType, File storePath, String storePass)
throws KeyStoreException, IOException, GeneralSecurityException {
KeyStore ks = KeyStore.getInstance(storeType);
FileInputStream stream = null;
try {
stream = new FileInputStream(storePath);
ks.load(stream, storePass.toCharArray());
} finally {
Util.closeQuietly(stream);
}
return ks;
}
private static String findAlias(KeyStore ks) throws KeyStoreException {
Enumeration<String> e = ks.aliases();
while(e.hasMoreElements()) {
String entry = e.nextElement();
if (ks.isKeyEntry(entry)) {
return entry;
}
}
throw new KeyStoreException("Cannot find a private key entry");
}
private static class ClientKeyStore {
private final X509Certificate[] certChain;
private final PrivateKey key;
private final String keyAlias;
ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException
{
this.keyAlias = keyAlias;
this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray());
Certificate[] certs = ks.getCertificateChain(this.keyAlias);
X509Certificate[] X509certs = new X509Certificate[certs.length];
for (int i=0; i < certs.length; i++) {
X509certs[i] = (X509Certificate) certs[i];
}
this.certChain = X509certs;
}
final X509Certificate[] getCertificateChain() {
return this.certChain;
}
final PrivateKey getPrivateKey() {
return this.key;
}
final String getAlias() {
return this.keyAlias;
}
}
private static class X509KeyManager extends X509ExtendedKeyManager {
private final ClientKeyStore keyStore;
X509KeyManager(final ClientKeyStore keyStore) {
this.keyStore = keyStore;
}
// Call sequence: 1
// @Override
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers,
Socket socket) {
return keyStore.getAlias();
}
// Call sequence: 2
// @Override
@Override
public X509Certificate[] getCertificateChain(String alias) {
return keyStore.getCertificateChain();
}
// @Override
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return new String[]{ keyStore.getAlias()};
}
// Call sequence: 3
// @Override
@Override
public PrivateKey getPrivateKey(String alias) {
return keyStore.getPrivateKey();
}
// @Override
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return null;
}
// @Override
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return null;
}
}
}