OSDN Git Service

AndroidKeyStore: Add encrypted flag
[android-x86/frameworks-base.git] / keystore / java / android / security / AndroidKeyStore.java
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
17 package android.security;
18
19 import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
20 import org.apache.harmony.xnet.provider.jsse.OpenSSLKeyHolder;
21
22 import android.util.Log;
23
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.security.InvalidKeyException;
29 import java.security.Key;
30 import java.security.KeyStore.Entry;
31 import java.security.KeyStore.PrivateKeyEntry;
32 import java.security.KeyStore.ProtectionParameter;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.security.KeyStoreSpi;
36 import java.security.NoSuchAlgorithmException;
37 import java.security.PrivateKey;
38 import java.security.UnrecoverableKeyException;
39 import java.security.cert.Certificate;
40 import java.security.cert.CertificateEncodingException;
41 import java.security.cert.CertificateException;
42 import java.security.cert.CertificateFactory;
43 import java.security.cert.X509Certificate;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Date;
48 import java.util.Enumeration;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import 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  */
72 public 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             AndroidKeyStoreParameter 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 AndroidKeyStoreParameter)) {
548             throw new KeyStoreException("protParam should be AndroidKeyStoreParameter; was: "
549                     + param.getClass().getName());
550         }
551
552         if (entry instanceof PrivateKeyEntry) {
553             PrivateKeyEntry prE = (PrivateKeyEntry) entry;
554             setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
555                     (AndroidKeyStoreParameter) param);
556             return;
557         }
558
559         throw new KeyStoreException(
560                 "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry);
561     }
562
563 }