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