• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.OpenSSLDSAPrivateKey;
20 import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
21 import org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey;
22 
23 import android.util.Log;
24 
25 import java.io.ByteArrayInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.security.InvalidKeyException;
30 import java.security.Key;
31 import java.security.KeyStoreException;
32 import java.security.KeyStoreSpi;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.PrivateKey;
35 import java.security.UnrecoverableKeyException;
36 import java.security.cert.Certificate;
37 import java.security.cert.CertificateEncodingException;
38 import java.security.cert.CertificateException;
39 import java.security.cert.CertificateFactory;
40 import java.security.cert.X509Certificate;
41 import java.util.ArrayList;
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.Date;
45 import java.util.Enumeration;
46 import java.util.HashSet;
47 import java.util.Iterator;
48 import 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  */
69 public class AndroidKeyStore extends KeyStoreSpi {
70     public static final String NAME = "AndroidKeyStore";
71 
72     private android.security.KeyStore mKeyStore;
73 
74     @Override
engineGetKey(String alias, char[] password)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
engineGetCertificateChain(String alias)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
engineGetCertificate(String alias)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 
toCertificate(byte[] bytes)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")
toCertificates(byte[] bytes)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 
getModificationDate(String alias)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
engineGetCreationDate(String alias)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
engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)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 
setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain)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
engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)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
engineSetCertificateEntry(String alias, Certificate cert)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
engineDeleteEntry(String alias)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 
getUniqueAliases()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
engineAliases()398     public Enumeration<String> engineAliases() {
399         return Collections.enumeration(getUniqueAliases());
400     }
401 
402     @Override
engineContainsAlias(String alias)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
engineSize()414     public int engineSize() {
415         return getUniqueAliases().size();
416     }
417 
418     @Override
engineIsKeyEntry(String alias)419     public boolean engineIsKeyEntry(String alias) {
420         return isKeyEntry(alias);
421     }
422 
isKeyEntry(String alias)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 
isCertificateEntry(String alias)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
engineIsCertificateEntry(String alias)440     public boolean engineIsCertificateEntry(String alias) {
441         return !isKeyEntry(alias) && isCertificateEntry(alias);
442     }
443 
444     @Override
engineGetCertificateAlias(Certificate cert)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
engineStore(OutputStream stream, char[] password)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
engineLoad(InputStream stream, char[] password)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