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