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