AndroidKeyStoreSpi.java revision 6c03bf523d6b0e4edfade726ed0ee1c49c4d0f69
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.keystore;
18
19import com.android.org.conscrypt.OpenSSLEngine;
20import com.android.org.conscrypt.OpenSSLKeyHolder;
21
22import libcore.util.EmptyArray;
23
24import android.security.Credentials;
25import android.security.KeyStoreParameter;
26import android.security.keymaster.KeyCharacteristics;
27import android.security.keymaster.KeymasterArguments;
28import android.security.keymaster.KeymasterDefs;
29import android.security.keystore.KeyProperties;
30import android.security.keystore.KeyProtection;
31import android.util.Log;
32
33import java.io.ByteArrayInputStream;
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.OutputStream;
37import java.security.InvalidKeyException;
38import java.security.Key;
39import java.security.KeyStore.Entry;
40import java.security.KeyStore.PrivateKeyEntry;
41import java.security.KeyStore.ProtectionParameter;
42import java.security.KeyStore;
43import java.security.KeyStore.SecretKeyEntry;
44import java.security.KeyStoreException;
45import java.security.KeyStoreSpi;
46import java.security.NoSuchAlgorithmException;
47import java.security.PrivateKey;
48import java.security.UnrecoverableKeyException;
49import java.security.cert.Certificate;
50import java.security.cert.CertificateEncodingException;
51import java.security.cert.CertificateException;
52import java.security.cert.CertificateFactory;
53import java.security.cert.X509Certificate;
54import java.util.ArrayList;
55import java.util.Arrays;
56import java.util.Collection;
57import java.util.Collections;
58import java.util.Date;
59import java.util.Enumeration;
60import java.util.HashSet;
61import java.util.Iterator;
62import java.util.List;
63import java.util.Set;
64
65import javax.crypto.SecretKey;
66
67/**
68 * A java.security.KeyStore interface for the Android KeyStore. An instance of
69 * it can be created via the {@link java.security.KeyStore#getInstance(String)
70 * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
71 * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
72 * <p>
73 * This is built on top of Android's keystore daemon. The convention of alias
74 * use is:
75 * <p>
76 * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
77 * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
78 * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
79 * entry which will have the rest of the chain concatenated in BER format.
80 * <p>
81 * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
82 * with a single certificate.
83 *
84 * @hide
85 */
86public class AndroidKeyStoreSpi extends KeyStoreSpi {
87    public static final String NAME = "AndroidKeyStore";
88
89    private android.security.KeyStore mKeyStore;
90
91    @Override
92    public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
93            UnrecoverableKeyException {
94        if (isPrivateKeyEntry(alias)) {
95            final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
96            try {
97                return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
98            } catch (InvalidKeyException e) {
99                UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
100                t.initCause(e);
101                throw t;
102            }
103        } else if (isSecretKeyEntry(alias)) {
104            KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
105            String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias;
106            int errorCode = mKeyStore.getKeyCharacteristics(
107                    keyAliasInKeystore, null, null, keyCharacteristics);
108            if ((errorCode != KeymasterDefs.KM_ERROR_OK)
109                    && (errorCode != android.security.KeyStore.NO_ERROR)) {
110                throw (UnrecoverableKeyException)
111                        new UnrecoverableKeyException("Failed to load information about key")
112                                .initCause(mKeyStore.getInvalidKeyException(alias, errorCode));
113            }
114
115            int keymasterAlgorithm =
116                    keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
117            if (keymasterAlgorithm == -1) {
118                keymasterAlgorithm =
119                        keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ALGORITHM, -1);
120            }
121            if (keymasterAlgorithm == -1) {
122                throw new UnrecoverableKeyException("Key algorithm unknown");
123            }
124
125            List<Integer> keymasterDigests =
126                    keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST);
127            int keymasterDigest;
128            if (keymasterDigests.isEmpty()) {
129                keymasterDigest = -1;
130            } else {
131                // More than one digest can be permitted for this key. Use the first one to form the
132                // JCA key algorithm name.
133                keymasterDigest = keymasterDigests.get(0);
134            }
135
136            @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
137            try {
138                keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
139                        keymasterAlgorithm, keymasterDigest);
140            } catch (IllegalArgumentException e) {
141                throw (UnrecoverableKeyException)
142                        new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
143            }
144
145            return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmString);
146        }
147
148        return null;
149    }
150
151    @Override
152    public Certificate[] engineGetCertificateChain(String alias) {
153        if (alias == null) {
154            throw new NullPointerException("alias == null");
155        }
156
157        final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
158        if (leaf == null) {
159            return null;
160        }
161
162        final Certificate[] caList;
163
164        final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
165        if (caBytes != null) {
166            final Collection<X509Certificate> caChain = toCertificates(caBytes);
167
168            caList = new Certificate[caChain.size() + 1];
169
170            final Iterator<X509Certificate> it = caChain.iterator();
171            int i = 1;
172            while (it.hasNext()) {
173                caList[i++] = it.next();
174            }
175        } else {
176            caList = new Certificate[1];
177        }
178
179        caList[0] = leaf;
180
181        return caList;
182    }
183
184    @Override
185    public Certificate engineGetCertificate(String alias) {
186        if (alias == null) {
187            throw new NullPointerException("alias == null");
188        }
189
190        byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
191        if (certificate != null) {
192            return toCertificate(certificate);
193        }
194
195        certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
196        if (certificate != null) {
197            return toCertificate(certificate);
198        }
199
200        return null;
201    }
202
203    private static X509Certificate toCertificate(byte[] bytes) {
204        try {
205            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
206            return (X509Certificate) certFactory
207                    .generateCertificate(new ByteArrayInputStream(bytes));
208        } catch (CertificateException e) {
209            Log.w(NAME, "Couldn't parse certificate in keystore", e);
210            return null;
211        }
212    }
213
214    @SuppressWarnings("unchecked")
215    private static Collection<X509Certificate> toCertificates(byte[] bytes) {
216        try {
217            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
218            return (Collection<X509Certificate>) certFactory
219                    .generateCertificates(new ByteArrayInputStream(bytes));
220        } catch (CertificateException e) {
221            Log.w(NAME, "Couldn't parse certificates in keystore", e);
222            return new ArrayList<X509Certificate>();
223        }
224    }
225
226    private Date getModificationDate(String alias) {
227        final long epochMillis = mKeyStore.getmtime(alias);
228        if (epochMillis == -1L) {
229            return null;
230        }
231
232        return new Date(epochMillis);
233    }
234
235    @Override
236    public Date engineGetCreationDate(String alias) {
237        if (alias == null) {
238            throw new NullPointerException("alias == null");
239        }
240
241        Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
242        if (d != null) {
243            return d;
244        }
245
246        d = getModificationDate(Credentials.USER_SECRET_KEY + alias);
247        if (d != null) {
248            return d;
249        }
250
251        d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
252        if (d != null) {
253            return d;
254        }
255
256        return getModificationDate(Credentials.CA_CERTIFICATE + alias);
257    }
258
259    @Override
260    public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
261            throws KeyStoreException {
262        if ((password != null) && (password.length > 0)) {
263            throw new KeyStoreException("entries cannot be protected with passwords");
264        }
265
266        if (key instanceof PrivateKey) {
267            setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
268        } else if (key instanceof SecretKey) {
269            setSecretKeyEntry(alias, (SecretKey) key, null);
270        } else {
271            throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
272        }
273    }
274
275    private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
276            java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
277        int flags = 0;
278        KeyProtection spec;
279        if (param instanceof KeyStoreParameter) {
280            KeyStoreParameter legacySpec = (KeyStoreParameter) param;
281            try {
282                String keyAlgorithm = key.getAlgorithm();
283                KeyProtection.Builder specBuilder;
284                if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
285                    specBuilder =
286                            new KeyProtection.Builder(
287                                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
288                    specBuilder.setDigests(
289                            KeyProperties.DIGEST_NONE,
290                            KeyProperties.DIGEST_MD5,
291                            KeyProperties.DIGEST_SHA1,
292                            KeyProperties.DIGEST_SHA224,
293                            KeyProperties.DIGEST_SHA256,
294                            KeyProperties.DIGEST_SHA384,
295                            KeyProperties.DIGEST_SHA512);
296                } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
297                    specBuilder =
298                            new KeyProtection.Builder(
299                                    KeyProperties.PURPOSE_ENCRYPT
300                                    | KeyProperties.PURPOSE_DECRYPT
301                                    | KeyProperties.PURPOSE_SIGN
302                                    | KeyProperties.PURPOSE_VERIFY);
303                    specBuilder.setDigests(
304                            KeyProperties.DIGEST_NONE,
305                            KeyProperties.DIGEST_MD5,
306                            KeyProperties.DIGEST_SHA1,
307                            KeyProperties.DIGEST_SHA224,
308                            KeyProperties.DIGEST_SHA256,
309                            KeyProperties.DIGEST_SHA384,
310                            KeyProperties.DIGEST_SHA512);
311                    specBuilder.setSignaturePaddings(
312                            KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
313                    specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB);
314                    specBuilder.setEncryptionPaddings(
315                            KeyProperties.ENCRYPTION_PADDING_NONE,
316                            KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
317                    // Disable randomized encryption requirement to support encryption padding NONE
318                    // above.
319                    specBuilder.setRandomizedEncryptionRequired(false);
320                } else {
321                    throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm);
322                }
323                if (legacySpec.isEncryptionRequired()) {
324                    flags = android.security.KeyStore.FLAG_ENCRYPTED;
325                }
326                specBuilder.setUserAuthenticationRequired(false);
327
328                spec = specBuilder.build();
329            } catch (NullPointerException | IllegalArgumentException e) {
330                throw new KeyStoreException("Unsupported protection parameter", e);
331            }
332        } else if (param instanceof KeyProtection) {
333            spec = (KeyProtection) param;
334        } else if (param != null) {
335            throw new KeyStoreException(
336                    "Unsupported protection parameter class:" + param.getClass().getName()
337                    + ". Supported: " + KeyStoreParameter.class.getName() + ", "
338                    + KeyProtection.class.getName());
339        } else {
340            spec = null;
341        }
342
343        byte[] keyBytes = null;
344
345        final String pkeyAlias;
346        if (key instanceof OpenSSLKeyHolder) {
347            pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
348        } else {
349            pkeyAlias = null;
350        }
351
352        final boolean shouldReplacePrivateKey;
353        if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
354            final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
355            if (!alias.equals(keySubalias)) {
356                throw new KeyStoreException("Can only replace keys with same alias: " + alias
357                        + " != " + keySubalias);
358            }
359
360            shouldReplacePrivateKey = false;
361        } else {
362            // Make sure the PrivateKey format is the one we support.
363            final String keyFormat = key.getFormat();
364            if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
365                throw new KeyStoreException(
366                        "Only PrivateKeys that can be encoded into PKCS#8 are supported");
367            }
368
369            // Make sure we can actually encode the key.
370            keyBytes = key.getEncoded();
371            if (keyBytes == null) {
372                throw new KeyStoreException("PrivateKey has no encoding");
373            }
374
375            shouldReplacePrivateKey = true;
376        }
377
378        // Make sure the chain exists since this is a PrivateKey
379        if ((chain == null) || (chain.length == 0)) {
380            throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
381        }
382
383        // Do chain type checking.
384        X509Certificate[] x509chain = new X509Certificate[chain.length];
385        for (int i = 0; i < chain.length; i++) {
386            if (!"X.509".equals(chain[i].getType())) {
387                throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
388                        + i);
389            }
390
391            if (!(chain[i] instanceof X509Certificate)) {
392                throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
393                        + i);
394            }
395
396            x509chain[i] = (X509Certificate) chain[i];
397        }
398
399        final byte[] userCertBytes;
400        try {
401            userCertBytes = x509chain[0].getEncoded();
402        } catch (CertificateEncodingException e) {
403            throw new KeyStoreException("Couldn't encode certificate #1", e);
404        }
405
406        /*
407         * If we have a chain, store it in the CA certificate slot for this
408         * alias as concatenated DER-encoded certificates. These can be
409         * deserialized by {@link CertificateFactory#generateCertificates}.
410         */
411        final byte[] chainBytes;
412        if (chain.length > 1) {
413            /*
414             * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
415             * so we only need the certificates starting at index 1.
416             */
417            final byte[][] certsBytes = new byte[x509chain.length - 1][];
418            int totalCertLength = 0;
419            for (int i = 0; i < certsBytes.length; i++) {
420                try {
421                    certsBytes[i] = x509chain[i + 1].getEncoded();
422                    totalCertLength += certsBytes[i].length;
423                } catch (CertificateEncodingException e) {
424                    throw new KeyStoreException("Can't encode Certificate #" + i, e);
425                }
426            }
427
428            /*
429             * Serialize this into one byte array so we can later call
430             * CertificateFactory#generateCertificates to recover them.
431             */
432            chainBytes = new byte[totalCertLength];
433            int outputOffset = 0;
434            for (int i = 0; i < certsBytes.length; i++) {
435                final int certLength = certsBytes[i].length;
436                System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
437                outputOffset += certLength;
438                certsBytes[i] = null;
439            }
440        } else {
441            chainBytes = null;
442        }
443
444        /*
445         * Make sure we clear out all the appropriate types before trying to
446         * write.
447         */
448        if (shouldReplacePrivateKey) {
449            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
450        } else {
451            Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
452            Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
453        }
454
455        if (shouldReplacePrivateKey
456                && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes,
457                        android.security.KeyStore.UID_SELF, flags)) {
458            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
459            throw new KeyStoreException("Couldn't put private key in keystore");
460        } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes,
461                android.security.KeyStore.UID_SELF, flags)) {
462            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
463            throw new KeyStoreException("Couldn't put certificate #1 in keystore");
464        } else if (chainBytes != null
465                && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes,
466                        android.security.KeyStore.UID_SELF, flags)) {
467            Credentials.deleteAllTypesForAlias(mKeyStore, alias);
468            throw new KeyStoreException("Couldn't put certificate chain in keystore");
469        }
470    }
471
472    private void setSecretKeyEntry(String entryAlias, SecretKey key,
473            java.security.KeyStore.ProtectionParameter param)
474            throws KeyStoreException {
475        if ((param != null) && (!(param instanceof KeyProtection))) {
476            throw new KeyStoreException(
477                    "Unsupported protection parameter class: " + param.getClass().getName()
478                    + ". Supported: " + KeyProtection.class.getName());
479        }
480        KeyProtection params = (KeyProtection) param;
481
482        if (key instanceof AndroidKeyStoreSecretKey) {
483            // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
484            // overwrite its own entry.
485            String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias();
486            if (keyAliasInKeystore == null) {
487                throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
488            }
489            if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
490                throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
491                        + keyAliasInKeystore);
492            }
493            String keyEntryAlias =
494                    keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
495            if (!entryAlias.equals(keyEntryAlias)) {
496                throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
497                        + " alias: " + entryAlias + " != " + keyEntryAlias);
498            }
499            // This is the entry where this key is already stored. No need to do anything.
500            if (params != null) {
501                throw new KeyStoreException("Modifying KeyStore-backed key using protection"
502                        + " parameters not supported");
503            }
504            return;
505        }
506
507        if (params == null) {
508            throw new KeyStoreException(
509                    "Protection parameters must be specified when importing a symmetric key");
510        }
511
512        // Not a KeyStore-backed secret key -- import its key material into keystore.
513        String keyExportFormat = key.getFormat();
514        if (keyExportFormat == null) {
515            throw new KeyStoreException(
516                    "Only secret keys that export their key material are supported");
517        } else if (!"RAW".equals(keyExportFormat)) {
518            throw new KeyStoreException(
519                    "Unsupported secret key material export format: " + keyExportFormat);
520        }
521        byte[] keyMaterial = key.getEncoded();
522        if (keyMaterial == null) {
523            throw new KeyStoreException("Key did not export its key material despite supporting"
524                    + " RAW format export");
525        }
526
527        String keyAlgorithmString = key.getAlgorithm();
528        int keymasterAlgorithm;
529        int keymasterDigest;
530        try {
531            keymasterAlgorithm =
532                    KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(keyAlgorithmString);
533            keymasterDigest = KeyProperties.KeyAlgorithm.toKeymasterDigest(keyAlgorithmString);
534        } catch (IllegalArgumentException e) {
535            throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
536        }
537
538        KeymasterArguments args = new KeymasterArguments();
539        args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm);
540
541        int[] keymasterDigests;
542        if (params.isDigestsSpecified()) {
543            // Digest(s) specified in parameters
544            keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests());
545            if (keymasterDigest != -1) {
546                // Digest also specified in the JCA key algorithm name.
547                if (!com.android.internal.util.ArrayUtils.contains(
548                        keymasterDigests, keymasterDigest)) {
549                    throw new KeyStoreException("Key digest mismatch"
550                            + ". Key: " + keyAlgorithmString
551                            + ", parameter spec: " + Arrays.asList(params.getDigests()));
552                }
553                // When the key is read back from keystore we reconstruct the JCA key algorithm
554                // name from the KM_TAG_ALGORITHM and the first KM_TAG_DIGEST. Thus we need to
555                // ensure that the digest reflected in the JCA key algorithm name is the first
556                // KM_TAG_DIGEST tag.
557                if (keymasterDigests[0] != keymasterDigest) {
558                    // The first digest is not the one implied by the JCA key algorithm name.
559                    // Swap the implied digest with the first one.
560                    for (int i = 0; i < keymasterDigests.length; i++) {
561                        if (keymasterDigests[i] == keymasterDigest) {
562                            keymasterDigests[i] = keymasterDigests[0];
563                            keymasterDigests[0] = keymasterDigest;
564                            break;
565                        }
566                    }
567                }
568            }
569        } else {
570            // No digest specified in parameters
571            if (keymasterDigest != -1) {
572                // Digest specified in the JCA key algorithm name.
573                keymasterDigests = new int[] {keymasterDigest};
574            } else {
575                keymasterDigests = EmptyArray.INT;
576            }
577        }
578        args.addInts(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests);
579        if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
580            if (keymasterDigests.length == 0) {
581                throw new KeyStoreException("At least one digest algorithm must be specified"
582                        + " for key algorithm " + keyAlgorithmString);
583            }
584        }
585
586        @KeyProperties.PurposeEnum int purposes = params.getPurposes();
587        int[] keymasterBlockModes =
588                KeyProperties.BlockMode.allToKeymaster(params.getBlockModes());
589        if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
590                && (params.isRandomizedEncryptionRequired())) {
591            for (int keymasterBlockMode : keymasterBlockModes) {
592                if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) {
593                    throw new KeyStoreException(
594                            "Randomized encryption (IND-CPA) required but may be violated by block"
595                            + " mode: "
596                            + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
597                            + ". See KeyProtection documentation.");
598                }
599            }
600        }
601        for (int keymasterPurpose : KeyProperties.Purpose.allToKeymaster(purposes)) {
602            args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
603        }
604        args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
605        if (params.getSignaturePaddings().length > 0) {
606            throw new KeyStoreException("Signature paddings not supported for symmetric keys");
607        }
608        int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
609                params.getEncryptionPaddings());
610        args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
611        KeymasterUtils.addUserAuthArgs(args,
612                params.isUserAuthenticationRequired(),
613                params.getUserAuthenticationValidityDurationSeconds());
614        args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
615                (params.getKeyValidityStart() != null)
616                        ? params.getKeyValidityStart() : new Date(0));
617        args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
618                (params.getKeyValidityForOriginationEnd() != null)
619                        ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE));
620        args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
621                (params.getKeyValidityForConsumptionEnd() != null)
622                        ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
623
624        if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
625                && (!params.isRandomizedEncryptionRequired())) {
626            // Permit caller-provided IV when encrypting with this key
627            args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
628        }
629
630        Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
631        String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
632        int errorCode = mKeyStore.importKey(
633                keyAliasInKeystore,
634                args,
635                KeymasterDefs.KM_KEY_FORMAT_RAW,
636                keyMaterial,
637                0, // flags
638                new KeyCharacteristics());
639        if (errorCode != android.security.KeyStore.NO_ERROR) {
640            throw new KeyStoreException("Failed to import secret key. Keystore error code: "
641                + errorCode);
642        }
643    }
644
645    @Override
646    public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
647            throws KeyStoreException {
648        throw new KeyStoreException("Operation not supported because key encoding is unknown");
649    }
650
651    @Override
652    public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
653        if (isKeyEntry(alias)) {
654            throw new KeyStoreException("Entry exists and is not a trusted certificate");
655        }
656
657        // We can't set something to null.
658        if (cert == null) {
659            throw new NullPointerException("cert == null");
660        }
661
662        final byte[] encoded;
663        try {
664            encoded = cert.getEncoded();
665        } catch (CertificateEncodingException e) {
666            throw new KeyStoreException(e);
667        }
668
669        if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded,
670                android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) {
671            throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
672        }
673    }
674
675    @Override
676    public void engineDeleteEntry(String alias) throws KeyStoreException {
677        if (!engineContainsAlias(alias)) {
678            return;
679        }
680        // At least one entry corresponding to this alias exists in keystore
681
682        if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
683            throw new KeyStoreException("Failed to delete entry: " + alias);
684        }
685    }
686
687    private Set<String> getUniqueAliases() {
688        final String[] rawAliases = mKeyStore.saw("");
689        if (rawAliases == null) {
690            return new HashSet<String>();
691        }
692
693        final Set<String> aliases = new HashSet<String>(rawAliases.length);
694        for (String alias : rawAliases) {
695            final int idx = alias.indexOf('_');
696            if ((idx == -1) || (alias.length() <= idx)) {
697                Log.e(NAME, "invalid alias: " + alias);
698                continue;
699            }
700
701            aliases.add(new String(alias.substring(idx + 1)));
702        }
703
704        return aliases;
705    }
706
707    @Override
708    public Enumeration<String> engineAliases() {
709        return Collections.enumeration(getUniqueAliases());
710    }
711
712    @Override
713    public boolean engineContainsAlias(String alias) {
714        if (alias == null) {
715            throw new NullPointerException("alias == null");
716        }
717
718        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
719                || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias)
720                || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
721                || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
722    }
723
724    @Override
725    public int engineSize() {
726        return getUniqueAliases().size();
727    }
728
729    @Override
730    public boolean engineIsKeyEntry(String alias) {
731        return isKeyEntry(alias);
732    }
733
734    private boolean isKeyEntry(String alias) {
735        return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
736    }
737
738    private boolean isPrivateKeyEntry(String alias) {
739        if (alias == null) {
740            throw new NullPointerException("alias == null");
741        }
742
743        return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
744    }
745
746    private boolean isSecretKeyEntry(String alias) {
747        if (alias == null) {
748            throw new NullPointerException("alias == null");
749        }
750
751        return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias);
752    }
753
754    private boolean isCertificateEntry(String alias) {
755        if (alias == null) {
756            throw new NullPointerException("alias == null");
757        }
758
759        return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
760    }
761
762    @Override
763    public boolean engineIsCertificateEntry(String alias) {
764        return !isKeyEntry(alias) && isCertificateEntry(alias);
765    }
766
767    @Override
768    public String engineGetCertificateAlias(Certificate cert) {
769        if (cert == null) {
770            return null;
771        }
772
773        final Set<String> nonCaEntries = new HashSet<String>();
774
775        /*
776         * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
777         * says to only compare the first certificate in the chain which is
778         * equivalent to the USER_CERTIFICATE prefix for the Android keystore
779         * convention.
780         */
781        final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE);
782        if (certAliases != null) {
783            for (String alias : certAliases) {
784                final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
785                if (certBytes == null) {
786                    continue;
787                }
788
789                final Certificate c = toCertificate(certBytes);
790                nonCaEntries.add(alias);
791
792                if (cert.equals(c)) {
793                    return alias;
794                }
795            }
796        }
797
798        /*
799         * Look at all the TrustedCertificateEntry types. Skip all the
800         * PrivateKeyEntry we looked at above.
801         */
802        final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE);
803        if (certAliases != null) {
804            for (String alias : caAliases) {
805                if (nonCaEntries.contains(alias)) {
806                    continue;
807                }
808
809                final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
810                if (certBytes == null) {
811                    continue;
812                }
813
814                final Certificate c =
815                        toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
816                if (cert.equals(c)) {
817                    return alias;
818                }
819            }
820        }
821
822        return null;
823    }
824
825    @Override
826    public void engineStore(OutputStream stream, char[] password) throws IOException,
827            NoSuchAlgorithmException, CertificateException {
828        throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
829    }
830
831    @Override
832    public void engineLoad(InputStream stream, char[] password) throws IOException,
833            NoSuchAlgorithmException, CertificateException {
834        if (stream != null) {
835            throw new IllegalArgumentException("InputStream not supported");
836        }
837
838        if (password != null) {
839            throw new IllegalArgumentException("password not supported");
840        }
841
842        // Unfortunate name collision.
843        mKeyStore = android.security.KeyStore.getInstance();
844    }
845
846    @Override
847    public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
848            throws KeyStoreException {
849        if (entry == null) {
850            throw new KeyStoreException("entry == null");
851        }
852
853        Credentials.deleteAllTypesForAlias(mKeyStore, alias);
854
855        if (entry instanceof KeyStore.TrustedCertificateEntry) {
856            KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry;
857            engineSetCertificateEntry(alias, trE.getTrustedCertificate());
858            return;
859        }
860
861        if (entry instanceof PrivateKeyEntry) {
862            PrivateKeyEntry prE = (PrivateKeyEntry) entry;
863            setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param);
864        } else if (entry instanceof SecretKeyEntry) {
865            SecretKeyEntry secE = (SecretKeyEntry) entry;
866            setSecretKeyEntry(alias, secE.getSecretKey(), param);
867        } else {
868            throw new KeyStoreException(
869                    "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
870                    + "; was " + entry);
871        }
872    }
873
874}
875