/* * 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); } }