2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.security;
19 import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
20 import org.apache.harmony.xnet.provider.jsse.OpenSSLKeyHolder;
22 import android.util.Log;
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;
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.
59 * This is built on top of Android's keystore daemon. The convention of alias
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.
67 * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
68 * with a single certificate.
72 public class AndroidKeyStore extends KeyStoreSpi {
73 public static final String NAME = "AndroidKeyStore";
75 private android.security.KeyStore mKeyStore;
78 public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
79 UnrecoverableKeyException {
80 if (!isKeyEntry(alias)) {
84 final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
86 return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
87 } catch (InvalidKeyException e) {
88 UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
95 public Certificate[] engineGetCertificateChain(String alias) {
97 throw new NullPointerException("alias == null");
100 final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
105 final Certificate[] caList;
107 final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
108 if (caBytes != null) {
109 final Collection<X509Certificate> caChain = toCertificates(caBytes);
111 caList = new Certificate[caChain.size() + 1];
113 final Iterator<X509Certificate> it = caChain.iterator();
115 while (it.hasNext()) {
116 caList[i++] = it.next();
119 caList = new Certificate[1];
128 public Certificate engineGetCertificate(String alias) {
130 throw new NullPointerException("alias == null");
133 byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
134 if (certificate != null) {
135 return toCertificate(certificate);
138 certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
139 if (certificate != null) {
140 return toCertificate(certificate);
146 private static X509Certificate toCertificate(byte[] bytes) {
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);
157 @SuppressWarnings("unchecked")
158 private static Collection<X509Certificate> toCertificates(byte[] bytes) {
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>();
169 private Date getModificationDate(String alias) {
170 final long epochMillis = mKeyStore.getmtime(alias);
171 if (epochMillis == -1L) {
175 return new Date(epochMillis);
179 public Date engineGetCreationDate(String alias) {
181 throw new NullPointerException("alias == null");
184 Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
189 d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
194 return getModificationDate(Credentials.CA_CERTIFICATE + alias);
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");
204 if (key instanceof PrivateKey) {
205 setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
207 throw new KeyStoreException("Only PrivateKeys are supported");
211 private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
212 AndroidKeyStoreParameter params) throws KeyStoreException {
213 byte[] keyBytes = null;
215 final String pkeyAlias;
216 if (key instanceof OpenSSLKeyHolder) {
217 pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
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);
230 shouldReplacePrivateKey = false;
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");
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");
245 shouldReplacePrivateKey = true;
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");
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 #"
261 if (!(chain[i] instanceof X509Certificate)) {
262 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
266 x509chain[i] = (X509Certificate) chain[i];
269 final byte[] userCertBytes;
271 userCertBytes = x509chain[0].getEncoded();
272 } catch (CertificateEncodingException e) {
273 throw new KeyStoreException("Couldn't encode certificate #1", e);
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}.
281 final byte[] chainBytes;
282 if (chain.length > 1) {
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.
287 final byte[][] certsBytes = new byte[x509chain.length - 1][];
288 int totalCertLength = 0;
289 for (int i = 0; i < certsBytes.length; i++) {
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);
299 * Serialize this into one byte array so we can later call
300 * CertificateFactory#generateCertificates to recover them.
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;
315 * Make sure we clear out all the appropriate types before trying to
318 if (shouldReplacePrivateKey) {
319 Credentials.deleteAllTypesForAlias(mKeyStore, alias);
321 Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
324 final int flags = (params == null) ? 0 : params.getFlags();
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");
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");
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");
355 // We can't set something to null.
357 throw new NullPointerException("cert == null");
360 final byte[] encoded;
362 encoded = cert.getEncoded();
363 } catch (CertificateEncodingException e) {
364 throw new KeyStoreException(e);
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?");
374 public void engineDeleteEntry(String alias) throws KeyStoreException {
375 if (!isKeyEntry(alias) && !isCertificateEntry(alias)) {
379 if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
380 throw new KeyStoreException("No such entry " + alias);
384 private Set<String> getUniqueAliases() {
385 final String[] rawAliases = mKeyStore.saw("");
386 if (rawAliases == null) {
387 return new HashSet<String>();
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);
398 aliases.add(new String(alias.substring(idx + 1)));
405 public Enumeration<String> engineAliases() {
406 return Collections.enumeration(getUniqueAliases());
410 public boolean engineContainsAlias(String alias) {
412 throw new NullPointerException("alias == null");
415 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
416 || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
417 || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
421 public int engineSize() {
422 return getUniqueAliases().size();
426 public boolean engineIsKeyEntry(String alias) {
427 return isKeyEntry(alias);
430 private boolean isKeyEntry(String alias) {
432 throw new NullPointerException("alias == null");
435 return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
438 private boolean isCertificateEntry(String alias) {
440 throw new NullPointerException("alias == null");
443 return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
447 public boolean engineIsCertificateEntry(String alias) {
448 return !isKeyEntry(alias) && isCertificateEntry(alias);
452 public String engineGetCertificateAlias(Certificate cert) {
457 final Set<String> nonCaEntries = new HashSet<String>();
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
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) {
473 final Certificate c = toCertificate(certBytes);
474 nonCaEntries.add(alias);
476 if (cert.equals(c)) {
483 * Look at all the TrustedCertificateEntry types. Skip all the
484 * PrivateKeyEntry we looked at above.
486 final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE);
487 if (certAliases != null) {
488 for (String alias : caAliases) {
489 if (nonCaEntries.contains(alias)) {
493 final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
494 if (certBytes == null) {
498 final Certificate c =
499 toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
500 if (cert.equals(c)) {
510 public void engineStore(OutputStream stream, char[] password) throws IOException,
511 NoSuchAlgorithmException, CertificateException {
512 throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
516 public void engineLoad(InputStream stream, char[] password) throws IOException,
517 NoSuchAlgorithmException, CertificateException {
518 if (stream != null) {
519 throw new IllegalArgumentException("InputStream not supported");
522 if (password != null) {
523 throw new IllegalArgumentException("password not supported");
526 // Unfortunate name collision.
527 mKeyStore = android.security.KeyStore.getInstance();
531 public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
532 throws KeyStoreException {
534 throw new KeyStoreException("entry == null");
537 if (engineContainsAlias(alias)) {
538 engineDeleteEntry(alias);
541 if (entry instanceof KeyStore.TrustedCertificateEntry) {
542 KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry;
543 engineSetCertificateEntry(alias, trE.getTrustedCertificate());
547 if (param != null && !(param instanceof AndroidKeyStoreParameter)) {
548 throw new KeyStoreException("protParam should be AndroidKeyStoreParameter; was: "
549 + param.getClass().getName());
552 if (entry instanceof PrivateKeyEntry) {
553 PrivateKeyEntry prE = (PrivateKeyEntry) entry;
554 setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
555 (AndroidKeyStoreParameter) param);
559 throw new KeyStoreException(
560 "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry);