/* * Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved. * See LICENCE file for licensing information. */ package eu.emi.security.authn.x509.impl; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.Enumeration; import javax.crypto.BadPaddingException; import eu.emi.security.authn.x509.helpers.AbstractX509Credential; import eu.emi.security.authn.x509.helpers.CertificateHelpers; import eu.emi.security.authn.x509.helpers.KeyStoreHelper; /** * Wraps a Java KeyStore in form suitable for use in JSSE. * * @author K. Benedyczak */ public class KeystoreCredential extends AbstractX509Credential { /** * Reads a Java KeyStore to provide an interface suitable to use it * in JSSE. * * @param keystorePath keystore path * @param storePasswd keystore password * @param keyPasswd private key password * @param keyAlias private key alias or null. In case of null, alias will be autodetected, * however this will work only it the keystore contains exactly one key entry. * @param storeType type of the keystore, "JKS" or "PKCS12". null value is forbidden, * but if autodetection is desired the static autodetectType() method of this can be used. * @throws IOException if the keystore can not be read * @throws KeyStoreException if the keystore can not be parsed or if passwords are incorrect */ public KeystoreCredential(String keystorePath, char[] storePasswd, char[] keyPasswd, String keyAlias, String storeType) throws IOException, KeyStoreException { KeyStore loaded = loadKeystore(keystorePath, storePasswd, storeType); keyAlias = checkKeystore(loaded, keyPasswd, keyAlias); createSingleKeyView(loaded, keyAlias, keyPasswd); } protected KeyStore loadKeystore(String keystorePath, char[] storePasswd, String storeType) throws KeyStoreException, IOException { KeyStore ks = KeyStoreHelper.getInstanceForCredential(storeType); InputStream is = new BufferedInputStream(new FileInputStream(new File(keystorePath))); try { ks.load(is, storePasswd); return ks; } catch (NoSuchAlgorithmException e) { throw new KeyStoreException("Keystore contents is using " + "an unsupported algorithm", e); } catch (CertificateException e) { throw new KeyStoreException("Keystore certificate is invalid", e); } catch (IOException e) { if (e.getCause() != null && e.getCause() instanceof BadPaddingException) throw new KeyStoreException("Keystore key password is invalid or the keystore is corrupted.", e); throw e; } finally { is.close(); } } protected String checkKeystore(KeyStore ks, char[] keyPasswd, String keyAlias) throws KeyStoreException { try { if (keyAlias == null) keyAlias = getDefaultKeyAlias(ks); if (!ks.containsAlias(keyAlias)) throw new KeyStoreException("Key alias >" + keyAlias + "< does not exist in the keystore"); Key k = ks.getKey(keyAlias, keyPasswd); if (k == null) throw new KeyStoreException("Key alias >" + keyAlias + "< is not an alias of a key entry, but an alias of a certificate entry"); if (!(k instanceof PrivateKey)) throw new KeyStoreException("Key under the alias >" + keyAlias + "< is not a PrivateKey but " + k.getClass().getName()); Certificate c = ks.getCertificate(keyAlias); if (c == null) throw new KeyStoreException("There is no certificate associated with " + "the key under the alias >" + keyAlias + "<"); CertificateHelpers.checkKeysMatching((PrivateKey) k, c.getPublicKey()); return keyAlias; } catch (UnrecoverableKeyException e) { throw new KeyStoreException("Key's password seems to be incorrect", e); } catch (NoSuchAlgorithmException e) { throw new KeyStoreException("Key is encrypted or uses an unsupported algorithm", e); } catch (InvalidKeyException e) { throw new KeyStoreException("Key and certificate in the keystore are not matching", e); } } protected String getDefaultKeyAlias(KeyStore keystore) throws KeyStoreException { Enumeration<String> e = keystore.aliases(); String ret = null; while (e.hasMoreElements()) { String a = e.nextElement(); if (keystore.isKeyEntry(a)) { if (ret == null) ret = a; else throw new KeyStoreException("Key alias was not " + "provided and the keystore contains more then one key entry: " + a + " and " + ret); } } if (ret == null) throw new KeyStoreException("The keystore doesn't contain any key entry"); return ret; } protected void createSingleKeyView(KeyStore original, String alias, char[] password) { try { ks = KeyStoreHelper.getInstanceForCredential("JKS"); ks.load(null); Key key = original.getKey(alias, password); Certificate []chain = original.getCertificateChain(alias); ks.setKeyEntry(ALIAS, key, KEY_PASSWD, chain); } catch (Exception e) { throw new RuntimeException("Got error when loading data from the " + "correct original keystore - this is most probably a bug", e); } } /** * Tries to autodetect keystore type. * @param ksPath key store path * @param ksPassword key store password * @return Detected type * @throws IOException if error occurred when reading the file * @throws KeyStoreException if autodetection failed * */ public static String autodetectType(String ksPath, char[] ksPassword) throws IOException, KeyStoreException { File file = new File(ksPath); if (!file.exists()) throw new FileNotFoundException("Keystore file " + ksPath + " does not exist"); if (!file.isFile()) throw new IOException("Keystore specified with " + ksPath + " is not a file (is directory?)"); if (!file.canRead()) throw new IOException("Keystore specified with " + ksPath + " is not readable"); String guess; if (ksPath.endsWith("p12") || ksPath.endsWith("pkcs") || ksPath.endsWith("pkcs12")) guess = "PKCS12"; else guess = "JKS"; if (tryLoadKs(guess, ksPath, ksPassword)) return guess; if (guess.equals("JKS")) guess = "PKCS12"; else guess = "JKS"; if (tryLoadKs(guess, ksPath, ksPassword)) return guess; throw new KeyStoreException("Autodetection of keystore type failed. " + "Most probably it is not a valid JKS or PKCS12 file."); } private static boolean tryLoadKs(String type, String ksPath, char[] ksPassword) { InputStream is = null; try { KeyStore ks = KeyStoreHelper.getInstanceForCredential(type); is = new BufferedInputStream(new FileInputStream(ksPath)); ks.load(is, ksPassword); } catch (IOException e) { if (e.getCause() != null && e.getCause() instanceof UnrecoverableKeyException) { //password is wrong but it seems that type correct return true; } return false; } catch (Exception e) { return false; } finally { if (is != null) try { is.close(); } catch (IOException e) { //we did our best } } return true; } }