/*
* This file is part of WebScarab, an Open Web Application Security
* Project utility. For details, please see http://www.owasp.org/
*
* Copyright (c) 2002 - 2004 Rogan Dawes
*
* Please note that this file was originally released under the
* GNU General Public License as published by the Free Software Foundation;
* either version 2 of the License, or (at your option) any later version.
*
* As of October 2014 Rogan Dawes granted the OWASP ZAP Project permission to
* redistribute this code under the Apache License, Version 2.0:
*
*
* Licensed 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 ch.csnc.extension.httpclient;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.log4j.Logger;
import ch.csnc.extension.util.Encoding;
public class SSLContextManager {
/**
* The canonical class name of Sun PKCS#11 Provider.
*/
public static final String SUN_PKCS11_CANONICAL_CLASS_NAME = "sun.security.pkcs11.SunPKCS11";
/**
* The canonical class name of IBMPKCS11Impl Provider.
*/
public static final String IBM_PKCS11_CONONICAL_CLASS_NAME = "com.ibm.crypto.pkcs11impl.provider.IBMPKCS11Impl";
/**
* The name for providers of type PKCS#11.
*
* @see #isProviderAvailable(String)
*/
public static final String PKCS11_PROVIDER_TYPE = "PKCS11";
/**
* The name of the {@code KeyStore} type of Sun PKCS#11 Provider.
*
* @see KeyStore#getInstance(String, Provider)
*/
private static final String SUN_PKCS11_KEYSTORE_TYPE = "PKCS11";
/**
* The name of the {@code KeyStore} type of IBMPKCS11Impl Provider.
*
* @see KeyStore#getInstance(String, Provider)
*/
private static final String IBM_PKCS11_KEYSTORE_TYPE = "PKCS11IMPLKS";
private Map<String, SSLContext> _contextMaps = new TreeMap<String, SSLContext>();
private SSLContext _noClientCertContext;
private String _defaultKey = null;
private Map<String, Map<?, ?>> _aliasPasswords = new HashMap<String, Map<?, ?>>();
private List<KeyStore> _keyStores = new ArrayList<KeyStore>();
private Map<KeyStore, String> _keyStoreDescriptions = new HashMap<KeyStore, String>();
private Map<KeyStore, String> _keyStorePasswords = new HashMap<KeyStore, String>();
private static Logger log = Logger.getLogger(SSLContextManager.class);
private static TrustManager[] _trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
private int _defaultKeystoreIndex = -1;
private int _defaultAliasIndex = -1;
/** Creates a new instance of SSLContextManager */
public SSLContextManager() {
try {
_noClientCertContext = SSLContext.getInstance("SSL");
_noClientCertContext.init(null, _trustAllCerts, new SecureRandom());
} catch (NoSuchAlgorithmException nsao) {
log.error("Could not get an instance of the SSL algorithm: " + nsao.getMessage(), nsao);
} catch (KeyManagementException kme) {
log.error("Error initialising the SSL Context: " + kme.getMessage(), kme);
}
try {
initMSCAPI();
} catch (Exception e) {
}
}
public boolean isProviderAvailable(String type) {
try {
if (type.equals(PKCS11_PROVIDER_TYPE)) {
try {
Class.forName(SUN_PKCS11_CANONICAL_CLASS_NAME);
return true;
} catch (Throwable ignore) {
Class.forName(IBM_PKCS11_CONONICAL_CLASS_NAME);
return true;
}
} else if (type.equals("msks")) {
Class.forName("se.assembla.jce.provider.ms.MSProvider");
return true;
}
} catch (Throwable ignore) {
}
return false;
}
private int addKeyStore(KeyStore ks, String description, String password) {
int index = _keyStores.indexOf(ks);
if (index == -1) {
_keyStores.add(ks);
index = _keyStores.size() - 1;
}
_keyStoreDescriptions.put(ks, description);
_keyStorePasswords.put(ks, password);
return index;
}
public boolean removeKeyStore(int keystoreIndex) {
boolean isDefaultKeyStore = (keystoreIndex == _defaultKeystoreIndex);
KeyStore ks = _keyStores.get(keystoreIndex);
_keyStores.remove(ks);
_keyStoreDescriptions.remove(ks);
_keyStorePasswords.remove(ks);
if (isDefaultKeyStore) {
_defaultKeystoreIndex = -1;
_defaultAliasIndex = -1;
}
return isDefaultKeyStore;
}
public int getKeyStoreCount() {
return _keyStores.size();
}
public String getKeyStoreDescription(int keystoreIndex) {
return _keyStoreDescriptions.get(_keyStores.get(keystoreIndex));
}
public String getKeyStorePassword(int keystoreIndex) {
return _keyStorePasswords.get(_keyStores.get(keystoreIndex));
}
public int getAliasCount(int keystoreIndex) {
return getAliases(_keyStores.get(keystoreIndex)).size();
}
public String getAliasAt(int keystoreIndex, int aliasIndex) {
return getAliases(_keyStores.get(keystoreIndex)).get(aliasIndex).getAlias();
}
private List<AliasCertificate> getAliases(KeyStore ks) {
List<AliasCertificate> aliases = new ArrayList<AliasCertificate>();
try {
Enumeration<String> en = ks.aliases();
boolean isIbm = isIbmPKCS11Provider();
while (en.hasMoreElements()) {
String alias = en.nextElement();
// Sun's and IBM's KeyStore implementations behave differently...
// With IBM's KeyStore impl #getCertificate(String) returns null when #isKeyEntry(String) returns true.
// If IBM add all certificates and let the user choose the correct one.
if (ks.isKeyEntry(alias) || (isIbm && ks.isCertificateEntry(alias))) {
Certificate cert = ks.getCertificate(alias);
// IBM: Maybe we should check the KeyUsage?
// ((X509Certificate) cert).getKeyUsage()[0]
AliasCertificate aliasCert = new AliasCertificate(cert, alias);
aliases.add(aliasCert);
}
}
} catch (KeyStoreException kse) {
kse.printStackTrace();
}
return aliases;
}
public List<AliasCertificate> getAliases(int ks) {
return getAliases(_keyStores.get(ks));
}
public Certificate getCertificate(int keystoreIndex, int aliasIndex) {
try {
KeyStore ks = _keyStores.get(keystoreIndex);
String alias = getAliasAt(keystoreIndex, aliasIndex);
return ks.getCertificate(alias);
} catch (Exception e) {
return null;
}
}
public String getFingerPrint(Certificate cert) throws KeyStoreException {
if (!(cert instanceof X509Certificate)) {
return null;
}
StringBuffer buff = new StringBuffer();
X509Certificate x509 = (X509Certificate) cert;
try {
String fingerprint = Encoding.hashMD5(cert.getEncoded());
for (int i = 0; i < fingerprint.length(); i += 2) {
buff.append(fingerprint.substring(i, i + 1)).append(":");
}
buff.deleteCharAt(buff.length() - 1);
} catch (CertificateEncodingException e) {
throw new KeyStoreException(e.getMessage());
}
String dn = x509.getSubjectDN().getName();
log.info("Fingerprint is " + buff.toString().toUpperCase());
return buff.toString().toUpperCase() + " " + dn;
}
public boolean isKeyUnlocked(int keystoreIndex, int aliasIndex) {
KeyStore ks = _keyStores.get(keystoreIndex);
String alias = getAliasAt(keystoreIndex, aliasIndex);
Map<?, ?> pwmap = _aliasPasswords.get(ks);
if (pwmap == null) {
return false;
}
return pwmap.containsKey(alias);
}
public void setDefaultKey(int keystoreIndex, int aliasIndex) throws KeyStoreException {
_defaultKeystoreIndex = keystoreIndex;
_defaultAliasIndex = aliasIndex;
if ((_defaultKeystoreIndex == -1) || (_defaultAliasIndex == -1)) {
_defaultKey = "";
} else {
_defaultKey = getFingerPrint(getCertificate(keystoreIndex, aliasIndex));
}
}
public String getDefaultKey() {
return _defaultKey;
}
public Certificate getDefaultCertificate() {
return getCertificate(_defaultKeystoreIndex, _defaultAliasIndex);
}
public int initMSCAPI() throws KeyStoreException, NoSuchProviderException, IOException, NoSuchAlgorithmException, CertificateException {
try {
if (!isProviderAvailable("msks")) {
return -1;
}
Provider mscapi = (Provider) Class.forName("se.assembla.jce.provider.ms.MSProvider").newInstance();
Security.addProvider(mscapi);
// init the key store
KeyStore ks = KeyStore.getInstance("msks", "assembla");
ks.load(null, null);
return addKeyStore(ks, "Microsoft CAPI Store", null);
} catch (Exception e) {
log.error("Error instantiating the MSCAPI provider: " + e.getMessage(), e);
return -1;
}
}
/*
* public int initCryptoApi() throws KeyStoreException,
* NoSuchAlgorithmException, CertificateException, IOException{
*
* Provider mscapi = new sun.security.mscapi.SunMSCAPI();
* Security.addProvider(mscapi);
*
* KeyStore ks = KeyStore.getInstance("Windows-MY"); ks.load(null, null);
*
* return addKeyStore(ks, "CryptoAPI", null); }
*/
public int initPKCS11(PKCS11Configuration configuration, String kspassword)
throws IOException, KeyStoreException,
CertificateException, NoSuchAlgorithmException,
ClassNotFoundException, SecurityException, NoSuchMethodException,
IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException {
if (!isProviderAvailable(PKCS11_PROVIDER_TYPE)) {
return -1;
}
Provider pkcs11 = createPKCS11Provider(configuration);
Security.addProvider(pkcs11);
// init the key store
KeyStore ks = getPKCS11KeyStore(pkcs11.getName());
ks.load(null, kspassword == null ? null : kspassword.toCharArray());
return addKeyStore(ks, "PKCS#11: " + configuration.getName(), ""); // do not store pin code
}
private static Provider createPKCS11Provider(PKCS11Configuration configuration) throws ClassNotFoundException,
NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Provider pkcs11 = null;
if (isSunPKCS11Provider()) {
pkcs11 = createInstance(SUN_PKCS11_CANONICAL_CLASS_NAME, InputStream.class, configuration.toInpuStream());
} else if (isIbmPKCS11Provider()) {
pkcs11 = createInstance(IBM_PKCS11_CONONICAL_CLASS_NAME, BufferedReader.class, new BufferedReader(
new InputStreamReader(configuration.toInpuStream())));
}
return pkcs11;
}
private static Provider createInstance(String name, Class<?> paramClass, Object param) throws ClassNotFoundException,
NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> instanceClass = Class.forName(name);
Constructor<?> c = instanceClass.getConstructor(new Class<?>[] { paramClass });
return (Provider) c.newInstance(new Object[] { param });
}
private static boolean isSunPKCS11Provider() {
try {
Class.forName(SUN_PKCS11_CANONICAL_CLASS_NAME);
return true;
} catch (Throwable ignore) {
}
return false;
}
private static boolean isIbmPKCS11Provider() {
try {
Class.forName(IBM_PKCS11_CONONICAL_CLASS_NAME);
return true;
} catch (Throwable ignore) {
}
return false;
}
private static KeyStore getPKCS11KeyStore(String providerName) throws KeyStoreException {
String keyStoreType = SUN_PKCS11_KEYSTORE_TYPE;
if (isIbmPKCS11Provider()) {
keyStoreType = IBM_PKCS11_KEYSTORE_TYPE;
}
return KeyStore.getInstance(keyStoreType, Security.getProvider(providerName));
}
public int loadPKCS12Certificate(String filename, String ksPassword)
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
// Get Filename
File file = new File(filename);
if (!file.exists()) {
throw new FileNotFoundException(filename + " could not be found");
}
String name = file.getName();
// Open the file
try (InputStream is = new FileInputStream(file)) {
// create the keystore
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(is, ksPassword == null ? null : ksPassword.toCharArray());
return addKeyStore(ks, "PKCS#12: " + name, ksPassword);
}
}
public boolean unlockKeyWithDefaultPassword(int keystoreIndex, int aliasIndex)
throws KeyManagementException, KeyStoreException {
return unlockKey(keystoreIndex, aliasIndex, getKeyStorePassword(keystoreIndex));
}
public boolean unlockKey(int keystoreIndex, int aliasIndex, String keyPassword)
throws KeyStoreException, KeyManagementException {
KeyStore ks = _keyStores.get(keystoreIndex);
String alias = getAliasAt(keystoreIndex, aliasIndex);
AliasKeyManager akm = new AliasKeyManager(ks, alias, keyPassword);
try {
akm.getPrivateKey(alias).toString();
} catch (NullPointerException ex) {
log.error("Could not get private key: " + ex.getMessage(), ex);
return false;
}
String fingerprint = getFingerPrint(getCertificate(keystoreIndex, aliasIndex));
if (fingerprint == null) {
log.info("No fingerprint found");
return false;
}
SSLContext sc;
try {
sc = SSLContext.getInstance("SSL");
} catch (NoSuchAlgorithmException nsao) {
log.error("Could not get an instance of the SSL algorithm: " + nsao.getMessage(), nsao);
return false;
}
sc.init(new KeyManager[] { akm }, _trustAllCerts, new SecureRandom());
String key = fingerprint;
if (key.indexOf(" ") > 0) {
key = key.substring(0, key.indexOf(" "));
}
_contextMaps.put(key, sc);
log.info("Key has been unlocked.");
return true;
}
public void invalidateSessions() {
invalidateSession(_noClientCertContext);
Iterator<String> it = _contextMaps.keySet().iterator();
while (it.hasNext()) {
invalidateSession(_contextMaps.get(it.next()));
}
}
private void invalidateSession(SSLContext sc) {
SSLSessionContext sslsc = sc.getClientSessionContext();
if (sslsc != null) {
int timeout = sslsc.getSessionTimeout();
// force sessions to be timed out
sslsc.setSessionTimeout(1);
sslsc.setSessionTimeout(timeout);
}
sslsc = sc.getServerSessionContext();
if (sslsc != null) {
int timeout = sslsc.getSessionTimeout();
// force sessions to be timed out
sslsc.setSessionTimeout(1);
sslsc.setSessionTimeout(timeout);
}
}
public SSLContext getSSLContext(String fingerprint) {
log.info("Requested SSLContext for " + fingerprint);
if (fingerprint == null || fingerprint.equals("none")) {
return _noClientCertContext;
}
if (fingerprint.indexOf(" ") > 0) {
fingerprint = fingerprint.substring(0, fingerprint.indexOf(" "));
}
return _contextMaps.get(fingerprint);
}
}