/* * 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.conscrypt; import java.net.Socket; import java.security.KeyStore; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; import java.security.UnrecoverableEntryException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Locale; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedKeyManager; import javax.security.auth.x500.X500Principal; /** * KeyManager implementation. * * This implementation uses hashed key store information. It works faster than retrieving all of the * data from the key store. Any key store changes, that happen after key manager was created, have * no effect. The implementation does not use peer information (host, port) that may be obtained * from socket or engine. * * @see javax.net.ssl.KeyManager * */ public class KeyManagerImpl extends X509ExtendedKeyManager { // hashed key store information private final Hashtable hash; /** * Creates Key manager * * @param keyStore * @param pwd */ public KeyManagerImpl(KeyStore keyStore, char[] pwd) { this.hash = new Hashtable(); final Enumeration aliases; try { aliases = keyStore.aliases(); } catch (KeyStoreException e) { return; } for (; aliases.hasMoreElements();) { final String alias = aliases.nextElement(); try { if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore .getEntry(alias, new KeyStore.PasswordProtection(pwd)); hash.put(alias, entry); } } catch (KeyStoreException e) { continue; } catch (UnrecoverableEntryException e) { continue; } catch (NoSuchAlgorithmException e) { continue; } } } @Override public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { final String[] al = chooseAlias(keyTypes, issuers); return (al == null ? null : al[0]); } @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { final String[] al = chooseAlias(new String[] { keyType }, issuers); return (al == null ? null : al[0]); } @Override public X509Certificate[] getCertificateChain(String alias) { if (alias == null) { return null; } if (hash.containsKey(alias)) { Certificate[] certs = hash.get(alias).getCertificateChain(); if (certs[0] instanceof X509Certificate) { X509Certificate[] xcerts = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { xcerts[i] = (X509Certificate) certs[i]; } return xcerts; } } return null; } @Override public String[] getClientAliases(String keyType, Principal[] issuers) { return chooseAlias(new String[] { keyType }, issuers); } @Override public String[] getServerAliases(String keyType, Principal[] issuers) { return chooseAlias(new String[] { keyType }, issuers); } @Override public PrivateKey getPrivateKey(String alias) { if (alias == null) { return null; } if (hash.containsKey(alias)) { return hash.get(alias).getPrivateKey(); } return null; } @Override public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine) { final String[] al = chooseAlias(keyTypes, issuers); return (al == null ? null : al[0]); } @Override public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { final String[] al = chooseAlias(new String[] { keyType }, issuers); return (al == null ? null : al[0]); } private String[] chooseAlias(String[] keyTypes, Principal[] issuers) { if (keyTypes == null || keyTypes.length == 0) { return null; } List issuersList = (issuers == null) ? null : Arrays.asList(issuers); ArrayList found = new ArrayList(); for (Enumeration aliases = hash.keys(); aliases.hasMoreElements();) { final String alias = aliases.nextElement(); final KeyStore.PrivateKeyEntry entry = hash.get(alias); final Certificate[] chain = entry.getCertificateChain(); final Certificate cert = chain[0]; final String certKeyAlg = cert.getPublicKey().getAlgorithm(); final String certSigAlg = (cert instanceof X509Certificate ? ((X509Certificate) cert).getSigAlgName().toUpperCase(Locale.US) : null); for (String keyAlgorithm : keyTypes) { if (keyAlgorithm == null) { continue; } final String sigAlgorithm; // handle cases like EC_EC and EC_RSA int index = keyAlgorithm.indexOf('_'); if (index == -1) { sigAlgorithm = null; } else { sigAlgorithm = keyAlgorithm.substring(index + 1); keyAlgorithm = keyAlgorithm.substring(0, index); } // key algorithm does not match if (!certKeyAlg.equals(keyAlgorithm)) { continue; } /* * TODO find a more reliable test for signature * algorithm. Unfortunately value varies with * provider. For example for "EC" it could be * "SHA1WithECDSA" or simply "ECDSA". */ // sig algorithm does not match if (sigAlgorithm != null && certSigAlg != null && !certSigAlg.contains(sigAlgorithm)) { continue; } // no issuers to match, just add to return list and continue if (issuers == null || issuers.length == 0) { found.add(alias); continue; } // check that a certificate in the chain was issued by one of the specified issuers for (Certificate certFromChain : chain) { if (!(certFromChain instanceof X509Certificate)) { // skip non-X509Certificates continue; } X509Certificate xcertFromChain = (X509Certificate) certFromChain; /* * Note use of X500Principal from * getIssuerX500Principal as opposed to Principal * from getIssuerDN. Principal.equals test does * not work in the case where * xcertFromChain.getIssuerDN is a bouncycastle * org.bouncycastle.jce.X509Principal. */ X500Principal issuerFromChain = xcertFromChain.getIssuerX500Principal(); if (issuersList.contains(issuerFromChain)) { found.add(alias); } } } } if (!found.isEmpty()) { return found.toArray(new String[found.size()]); } return null; } }