1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.conscrypt; 18 19 import java.net.Socket; 20 import java.security.KeyStore; 21 import java.security.KeyStore.PrivateKeyEntry; 22 import java.security.KeyStoreException; 23 import java.security.NoSuchAlgorithmException; 24 import java.security.Principal; 25 import java.security.PrivateKey; 26 import java.security.UnrecoverableEntryException; 27 import java.security.cert.Certificate; 28 import java.security.cert.X509Certificate; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.Enumeration; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Locale; 35 import java.util.Map; 36 import javax.net.ssl.SSLEngine; 37 import javax.net.ssl.X509ExtendedKeyManager; 38 import javax.security.auth.x500.X500Principal; 39 40 /** 41 * KeyManager implementation. 42 * 43 * This implementation uses hashed key store information. It works faster than retrieving all of the 44 * data from the key store. Any key store changes, that happen after key manager was created, have 45 * no effect. The implementation does not use peer information (host, port) that may be obtained 46 * from socket or engine. 47 * 48 * @see javax.net.ssl.KeyManager 49 */ 50 class KeyManagerImpl extends X509ExtendedKeyManager { 51 52 // hashed key store information 53 private final HashMap<String, PrivateKeyEntry> hash; 54 55 /** 56 * Creates Key manager 57 */ 58 KeyManagerImpl(KeyStore keyStore, char[] pwd) { 59 this.hash = new HashMap<String, PrivateKeyEntry>(); 60 final Enumeration<String> aliases; 61 try { 62 aliases = keyStore.aliases(); 63 } catch (KeyStoreException e) { 64 return; 65 } 66 for (; aliases.hasMoreElements();) { 67 final String alias = aliases.nextElement(); 68 try { 69 if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { 70 final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore 71 .getEntry(alias, new KeyStore.PasswordProtection(pwd)); 72 hash.put(alias, entry); 73 } 74 } catch (KeyStoreException ignored) { 75 // Ignored. 76 } catch (UnrecoverableEntryException ignored) { 77 // Ignored. 78 } catch (NoSuchAlgorithmException ignored) { 79 // Ignored. 80 } 81 } 82 } 83 84 @Override 85 public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 86 final String[] al = chooseAlias(keyTypes, issuers); 87 return (al == null ? null : al[0]); 88 } 89 90 @Override 91 public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { 92 final String[] al = chooseAlias(new String[] { keyType }, issuers); 93 return (al == null ? null : al[0]); 94 } 95 96 @Override 97 public X509Certificate[] getCertificateChain(String alias) { 98 if (alias == null) { 99 return null; 100 } 101 if (hash.containsKey(alias)) { 102 Certificate[] certs = hash.get(alias).getCertificateChain(); 103 if (certs[0] instanceof X509Certificate) { 104 X509Certificate[] xcerts = new X509Certificate[certs.length]; 105 for (int i = 0; i < certs.length; i++) { 106 xcerts[i] = (X509Certificate) certs[i]; 107 } 108 return xcerts; 109 } 110 } 111 return null; 112 113 } 114 115 @Override 116 public String[] getClientAliases(String keyType, Principal[] issuers) { 117 return chooseAlias(new String[] { keyType }, issuers); 118 } 119 120 @Override 121 public String[] getServerAliases(String keyType, Principal[] issuers) { 122 return chooseAlias(new String[] { keyType }, issuers); 123 } 124 125 @Override 126 public PrivateKey getPrivateKey(String alias) { 127 if (alias == null) { 128 return null; 129 } 130 if (hash.containsKey(alias)) { 131 return hash.get(alias).getPrivateKey(); 132 } 133 return null; 134 } 135 136 @Override 137 public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine) { 138 final String[] al = chooseAlias(keyTypes, issuers); 139 return (al == null ? null : al[0]); 140 } 141 142 @Override 143 public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { 144 final String[] al = chooseAlias(new String[] { keyType }, issuers); 145 return (al == null ? null : al[0]); 146 } 147 148 private String[] chooseAlias(String[] keyTypes, Principal[] issuers) { 149 if (keyTypes == null || keyTypes.length == 0) { 150 return null; 151 } 152 List<Principal> issuersList = (issuers == null) ? null : Arrays.asList(issuers); 153 ArrayList<String> found = new ArrayList<String>(); 154 for (final Map.Entry<String, PrivateKeyEntry> entry : hash.entrySet()) { 155 final String alias = entry.getKey(); 156 final Certificate[] chain = entry.getValue().getCertificateChain(); 157 final Certificate cert = chain[0]; 158 final String certKeyAlg = cert.getPublicKey().getAlgorithm(); 159 final String certSigAlg = (cert instanceof X509Certificate 160 ? ((X509Certificate) cert).getSigAlgName().toUpperCase(Locale.US) 161 : null); 162 for (String keyAlgorithm : keyTypes) { 163 if (keyAlgorithm == null) { 164 continue; 165 } 166 final String sigAlgorithm; 167 // handle cases like EC_EC and EC_RSA 168 int index = keyAlgorithm.indexOf('_'); 169 if (index == -1) { 170 sigAlgorithm = null; 171 } else { 172 sigAlgorithm = keyAlgorithm.substring(index + 1); 173 keyAlgorithm = keyAlgorithm.substring(0, index); 174 } 175 // key algorithm does not match 176 if (!certKeyAlg.equals(keyAlgorithm)) { 177 continue; 178 } 179 /* 180 * TODO find a more reliable test for signature 181 * algorithm. Unfortunately value varies with 182 * provider. For example for "EC" it could be 183 * "SHA1WithECDSA" or simply "ECDSA". 184 */ 185 // sig algorithm does not match 186 if (sigAlgorithm != null && certSigAlg != null 187 && !certSigAlg.contains(sigAlgorithm)) { 188 continue; 189 } 190 // no issuers to match, just add to return list and continue 191 if (issuers == null || issuers.length == 0) { 192 found.add(alias); 193 continue; 194 } 195 // check that a certificate in the chain was issued by one of the specified issuers 196 for (Certificate certFromChain : chain) { 197 if (!(certFromChain instanceof X509Certificate)) { 198 // skip non-X509Certificates 199 continue; 200 } 201 X509Certificate xcertFromChain = (X509Certificate) certFromChain; 202 /* 203 * Note use of X500Principal from 204 * getIssuerX500Principal as opposed to Principal 205 * from getIssuerDN. Principal.equals test does 206 * not work in the case where 207 * xcertFromChain.getIssuerDN is a bouncycastle 208 * org.bouncycastle.jce.X509Principal. 209 */ 210 X500Principal issuerFromChain = xcertFromChain.getIssuerX500Principal(); 211 if (issuersList.contains(issuerFromChain)) { 212 found.add(alias); 213 } 214 } 215 } 216 } 217 if (!found.isEmpty()) { 218 return found.toArray(new String[found.size()]); 219 } 220 return null; 221 } 222 } 223