1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.security;
18
19import com.android.org.conscrypt.OpenSSLEngine;
20import com.android.org.conscrypt.OpenSSLKeyHolder;
21
22import android.util.Log;
23
24import java.io.ByteArrayInputStream;
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28import java.security.InvalidKeyException;
29import java.security.Key;
30import java.security.KeyStore.Entry;
31import java.security.KeyStore.PrivateKeyEntry;
32import java.security.KeyStore.ProtectionParameter;
33import java.security.KeyStore;
34import java.security.KeyStoreException;
35import java.security.KeyStoreSpi;
36import java.security.NoSuchAlgorithmException;
37import java.security.PrivateKey;
38import java.security.UnrecoverableKeyException;
39import java.security.cert.Certificate;
40import java.security.cert.CertificateEncodingException;
41import java.security.cert.CertificateException;
42import java.security.cert.CertificateFactory;
43import java.security.cert.X509Certificate;
44import java.util.ArrayList;
45import java.util.Collection;
46import java.util.Collections;
47import java.util.Date;
48import java.util.Enumeration;
49import java.util.HashSet;
50import java.util.Iterator;
51import java.util.Set;
52
53/**
54 * A java.security.KeyStore interface for the Android KeyStore. An instance of
55 * it can be created via the {@link java.security.KeyStore#getInstance(String)
56 * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
57 * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
58 * <p>
59 * This is built on top of Android's keystore daemon. The convention of alias
60 * use is:
61 * <p>
62 * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
63 * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
64 * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
65 * entry which will have the rest of the chain concatenated in BER format.
66 * <p>
67 * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
68 * with a single certificate.
69 *
70 * @hide
71 */
72public class AndroidKeyStore extends KeyStoreSpi {
73    public static final String NAME = "AndroidKeyStore";
74
75    private android.security.KeyStore mKeyStore;
76
77    @Override
78    public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
79            UnrecoverableKeyException {
80        if (!isKeyEntry(alias)) {
81            return null;
82        }
83
84        final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
85        try {
86            return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
87        } catch (InvalidKeyException e) {
88            UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
89            t.initCause(e);
90            throw t;
91        }
92    }
93
94    @Override
95    public Certificate[] engineGetCertificateChain(String alias) {
96        if (alias == null) {
97            throw new NullPointerException("alias == null");
98        }
99
100        final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
101        if (leaf == null) {
102            return null;
103        }
104
105        final Certificate[] caList;
106
107        final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
108        if (caBytes != null) {
109            final Collection<X509Certificate> caChain = toCertificates(caBytes);
110
111            caList = new Certificate[caChain.size() + 1];
112
113            final Iterator<X509Certificate> it = caChain.iterator();
114            int i = 1;
115            while (it.hasNext()) {
116                caList[i++] = it.next();
117            }
118        } else {
119            caList = new Certificate[1];
120        }
121
122        caList[0] = leaf;
123
124        return caList;
125    }
126
127    @Override
128    public Certificate engineGetCertificate(String alias) {
129        if (alias == null) {
130            throw new NullPointerException("alias == null");
131        }
132
133        byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
134        if (certificate != null) {
135            return toCertificate(certificate);
136        }
137
138        certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
139        if (certificate != null) {
140            return toCertificate(certificate);
141        }
142
143        return null;
144    }
145
146    private static X509Certificate toCertificate(byte[] bytes) {
147        try {
148            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
149            return (X509Certificate) certFactory
150                    .generateCertificate(new ByteArrayInputStream(bytes));
151        } catch (CertificateException e) {
152            Log.w(NAME, "Couldn't parse certificate in keystore", e);
153            return null;
154        }
155    }
156
157    @SuppressWarnings("unchecked")
158    private static Collection<X509Certificate> toCertificates(byte[] bytes) {
159        try {
160            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
161            return (Collection<X509Certificate>) certFactory
162                    .generateCertificates(new ByteArrayInputStream(bytes));
163        } catch (CertificateException e) {
164            Log.w(NAME, "Couldn't parse certificates in keystore", e);
165            return new ArrayList<X509Certificate>();
166        }
167    }
168
169    private Date getModificationDate(String alias) {
170        final long epochMillis = mKeyStore.getmtime(alias);
171        if (epochMillis == -1L) {
172            return null;
173        }
174
175        return new Date(epochMillis);
176    }
177
178    @Override
179    public Date engineGetCreationDate(String alias) {
180        if (alias == null) {
181            throw new NullPointerException("alias == null");
182        }
183
184        Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
185        if (d != null) {
186            return d;
187        }
188
189        d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
190        if (d != null) {
191            return d;
192        }
193
194        return getModificationDate(Credentials.CA_CERTIFICATE + alias);
195    }
196
197    @Override
198    public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
199            throws KeyStoreException {
200        if ((password != null) && (password.length > 0)) {
201            throw new KeyStoreException("entries cannot be protected with passwords");
202        }
203
204        if (key instanceof PrivateKey) {
205            setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
206        } else {
207            throw new KeyStoreException("Only PrivateKeys are supported");
208        }
209    }
210
211    private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
212            KeyStoreParameter params) throws KeyStoreException {
213        byte[] keyBytes = null;
214
215        final String pkeyAlias;
216        if (key instanceof OpenSSLKeyHolder) {
217            pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
218        } else {
219            pkeyAlias = null;
220        }
221
222        final boolean shouldReplacePrivateKey;
223        if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
224            final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
225            if (!alias.equals(keySubalias)) {
226                throw new KeyStoreException("Can only replace keys with same alias: " + alias
227                        + " != " + keySubalias);
228            }
229
230            shouldReplacePrivateKey = false;
231        } else {
232            // Make sure the PrivateKey format is the one we support.
233            final String keyFormat = key.getFormat();
234            if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
235                throw new KeyStoreException(
236                        "Only PrivateKeys that can be encoded into PKCS#8 are supported");
237            }
238
239            // Make sure we can actually encode the key.
240            keyBytes = key.getEncoded();
241            if (keyBytes == null) {
242                throw new KeyStoreException("PrivateKey has no encoding");
243            }
244
245            shouldReplacePrivateKey = true;
246        }
247
248        // Make sure the chain exists since this is a PrivateKey
249        if ((chain == null) || (chain.length == 0)) {
250            throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
251        }
252
253        // Do chain type checking.
254        X509Certificate[] x509chain = new X509Certificate[chain.length];
255        for (int i = 0; i < chain.length; i++) {
256            if (!"X.509".equals(chain[i].getType())) {
257                throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
258                        + i);
259            }
260
261            if (!(chain[i] instanceof X509Certificate)) {
262                throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
263                        + i);
264            }
265
266            x509chain[i] = (X509Certificate) chain[i];
267        }
268
269        final byte[] userCertBytes;
270        try {
271            userCertBytes = x509chain[0].getEncoded();
272        } catch (CertificateEncodingException e) {
273            throw new KeyStoreException("Couldn't encode certificate #1", e);
274        }
275
276        /*
277         * If we have a chain, store it in the CA certificate slot for this
278         * alias as concatenated DER-encoded certificates. These can be
279         * deserialized by {@link CertificateFactory#generateCertificates}.
280         */
281        final byte[] chainBytes;
282        if (chain.length > 1) {
283            /*
284             * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
285             * so we only need the certificates starting at index 1.
286             */
287            final byte[][] certsBytes = new byte[x509chain.length - 1][];
288            int totalCertLength = 0;
289            for (int i = 0; i < certsBytes.length; i++) {
290                try {
291                    certsBytes[i] = x509chain[i + 1].getEncoded();
292                    totalCertLength += certsBytes[i].length;
293                } catch (CertificateEncodingException e) {
294                    throw new KeyStoreException("Can't encode Certificate #" + i, e);
295                }
296            }
297
298            /*
299             * Serialize this into one byte array so we can later call
300             * CertificateFactory#generateCertificates to recover them.
301             */
302            chainBytes = new byte[totalCertLength];
303            int outputOffset = 0;
304            for (int i = 0; i < certsBytes.length; i++) {
305                final int certLength = certsBytes[i].length;
306                System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
307                outputOffset += certLength;
308                certsBytes[i] = null;
309            }
310        } else {
311            chainBytes = null;
312        }
313
314        /*
315         * Make sure we clear out all the appropriate types before trying to
316         * write.
317         */
318        if (shouldReplacePrivateKey) {
319            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
320        } else {
321            Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
322        }
323
324        final int flags = (params == null) ? 0 : params.getFlags();
325
326        if (shouldReplacePrivateKey
327                && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes,
328                        android.security.KeyStore.UID_SELF, flags)) {
329            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
330            throw new KeyStoreException("Couldn't put private key in keystore");
331        } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes,
332                android.security.KeyStore.UID_SELF, flags)) {
333            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
334            throw new KeyStoreException("Couldn't put certificate #1 in keystore");
335        } else if (chainBytes != null
336                && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes,
337                        android.security.KeyStore.UID_SELF, flags)) {
338            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
339            throw new KeyStoreException("Couldn't put certificate chain in keystore");
340        }
341    }
342
343    @Override
344    public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
345            throws KeyStoreException {
346        throw new KeyStoreException("Operation not supported because key encoding is unknown");
347    }
348
349    @Override
350    public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
351        if (isKeyEntry(alias)) {
352            throw new KeyStoreException("Entry exists and is not a trusted certificate");
353        }
354
355        // We can't set something to null.
356        if (cert == null) {
357            throw new NullPointerException("cert == null");
358        }
359
360        final byte[] encoded;
361        try {
362            encoded = cert.getEncoded();
363        } catch (CertificateEncodingException e) {
364            throw new KeyStoreException(e);
365        }
366
367        if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded,
368                android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) {
369            throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
370        }
371    }
372
373    @Override
374    public void engineDeleteEntry(String alias) throws KeyStoreException {
375        if (!isKeyEntry(alias) && !isCertificateEntry(alias)) {
376            return;
377        }
378
379        if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
380            throw new KeyStoreException("No such entry " + alias);
381        }
382    }
383
384    private Set<String> getUniqueAliases() {
385        final String[] rawAliases = mKeyStore.saw("");
386        if (rawAliases == null) {
387            return new HashSet<String>();
388        }
389
390        final Set<String> aliases = new HashSet<String>(rawAliases.length);
391        for (String alias : rawAliases) {
392            final int idx = alias.indexOf('_');
393            if ((idx == -1) || (alias.length() <= idx)) {
394                Log.e(NAME, "invalid alias: " + alias);
395                continue;
396            }
397
398            aliases.add(new String(alias.substring(idx + 1)));
399        }
400
401        return aliases;
402    }
403
404    @Override
405    public Enumeration<String> engineAliases() {
406        return Collections.enumeration(getUniqueAliases());
407    }
408
409    @Override
410    public boolean engineContainsAlias(String alias) {
411        if (alias == null) {
412            throw new NullPointerException("alias == null");
413        }
414
415        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
416                || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
417                || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
418    }
419
420    @Override
421    public int engineSize() {
422        return getUniqueAliases().size();
423    }
424
425    @Override
426    public boolean engineIsKeyEntry(String alias) {
427        return isKeyEntry(alias);
428    }
429
430    private boolean isKeyEntry(String alias) {
431        if (alias == null) {
432            throw new NullPointerException("alias == null");
433        }
434
435        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
436    }
437
438    private boolean isCertificateEntry(String alias) {
439        if (alias == null) {
440            throw new NullPointerException("alias == null");
441        }
442
443        return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
444    }
445
446    @Override
447    public boolean engineIsCertificateEntry(String alias) {
448        return !isKeyEntry(alias) && isCertificateEntry(alias);
449    }
450
451    @Override
452    public String engineGetCertificateAlias(Certificate cert) {
453        if (cert == null) {
454            return null;
455        }
456
457        final Set<String> nonCaEntries = new HashSet<String>();
458
459        /*
460         * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
461         * says to only compare the first certificate in the chain which is
462         * equivalent to the USER_CERTIFICATE prefix for the Android keystore
463         * convention.
464         */
465        final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE);
466        if (certAliases != null) {
467            for (String alias : certAliases) {
468                final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
469                if (certBytes == null) {
470                    continue;
471                }
472
473                final Certificate c = toCertificate(certBytes);
474                nonCaEntries.add(alias);
475
476                if (cert.equals(c)) {
477                    return alias;
478                }
479            }
480        }
481
482        /*
483         * Look at all the TrustedCertificateEntry types. Skip all the
484         * PrivateKeyEntry we looked at above.
485         */
486        final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE);
487        if (certAliases != null) {
488            for (String alias : caAliases) {
489                if (nonCaEntries.contains(alias)) {
490                    continue;
491                }
492
493                final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
494                if (certBytes == null) {
495                    continue;
496                }
497
498                final Certificate c =
499                        toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
500                if (cert.equals(c)) {
501                    return alias;
502                }
503            }
504        }
505
506        return null;
507    }
508
509    @Override
510    public void engineStore(OutputStream stream, char[] password) throws IOException,
511            NoSuchAlgorithmException, CertificateException {
512        throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
513    }
514
515    @Override
516    public void engineLoad(InputStream stream, char[] password) throws IOException,
517            NoSuchAlgorithmException, CertificateException {
518        if (stream != null) {
519            throw new IllegalArgumentException("InputStream not supported");
520        }
521
522        if (password != null) {
523            throw new IllegalArgumentException("password not supported");
524        }
525
526        // Unfortunate name collision.
527        mKeyStore = android.security.KeyStore.getInstance();
528    }
529
530    @Override
531    public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
532            throws KeyStoreException {
533        if (entry == null) {
534            throw new KeyStoreException("entry == null");
535        }
536
537        if (engineContainsAlias(alias)) {
538            engineDeleteEntry(alias);
539        }
540
541        if (entry instanceof KeyStore.TrustedCertificateEntry) {
542            KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry;
543            engineSetCertificateEntry(alias, trE.getTrustedCertificate());
544            return;
545        }
546
547        if (param != null && !(param instanceof KeyStoreParameter)) {
548            throw new KeyStoreException(
549                    "protParam should be android.security.KeyStoreParameter; was: "
550                    + param.getClass().getName());
551        }
552
553        if (entry instanceof PrivateKeyEntry) {
554            PrivateKeyEntry prE = (PrivateKeyEntry) entry;
555            setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
556                    (KeyStoreParameter) param);
557            return;
558        }
559
560        throw new KeyStoreException(
561                "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry);
562    }
563
564}
565