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