Home | History | Annotate | Download | only in keystore
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.security.keystore;
     18 
     19 import libcore.util.EmptyArray;
     20 import android.security.Credentials;
     21 import android.security.GateKeeper;
     22 import android.security.KeyStore;
     23 import android.security.KeyStoreParameter;
     24 import android.security.keymaster.KeyCharacteristics;
     25 import android.security.keymaster.KeymasterArguments;
     26 import android.security.keymaster.KeymasterDefs;
     27 import android.security.keystore.KeyProperties;
     28 import android.security.keystore.KeyProtection;
     29 import android.security.keystore.SecureKeyImportUnavailableException;
     30 import android.security.keystore.WrappedKeyEntry;
     31 import android.util.Log;
     32 
     33 import java.io.ByteArrayInputStream;
     34 import java.io.IOException;
     35 import java.io.InputStream;
     36 import java.io.OutputStream;
     37 import java.security.Key;
     38 import java.security.KeyStore.Entry;
     39 import java.security.KeyStore.LoadStoreParameter;
     40 import java.security.KeyStore.PrivateKeyEntry;
     41 import java.security.KeyStore.ProtectionParameter;
     42 import java.security.KeyStore.SecretKeyEntry;
     43 import java.security.KeyStoreException;
     44 import java.security.KeyStoreSpi;
     45 import java.security.NoSuchAlgorithmException;
     46 import java.security.PrivateKey;
     47 import java.security.ProviderException;
     48 import java.security.PublicKey;
     49 import java.security.UnrecoverableKeyException;
     50 import java.security.cert.Certificate;
     51 import java.security.cert.CertificateEncodingException;
     52 import java.security.cert.CertificateException;
     53 import java.security.cert.CertificateFactory;
     54 import java.security.cert.X509Certificate;
     55 import java.util.ArrayList;
     56 import java.util.Arrays;
     57 import java.util.Collection;
     58 import java.util.Collections;
     59 import java.util.Date;
     60 import java.util.Enumeration;
     61 import java.util.HashSet;
     62 import java.util.Iterator;
     63 import java.util.Set;
     64 
     65 import 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  */
     86 public class AndroidKeyStoreSpi extends KeyStoreSpi {
     87     public static final String NAME = "AndroidKeyStore";
     88 
     89     private KeyStore mKeyStore;
     90     private int mUid = KeyStore.UID_SELF;
     91 
     92     @Override
     93     public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
     94             UnrecoverableKeyException {
     95         String userKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
     96         if (!mKeyStore.contains(userKeyAlias, mUid)) {
     97             // try legacy prefix for backward compatibility
     98             userKeyAlias = Credentials.USER_SECRET_KEY + alias;
     99             if (!mKeyStore.contains(userKeyAlias, mUid)) return null;
    100         }
    101         return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, userKeyAlias,
    102                 mUid);
    103     }
    104 
    105     @Override
    106     public Certificate[] engineGetCertificateChain(String alias) {
    107         if (alias == null) {
    108             throw new NullPointerException("alias == null");
    109         }
    110 
    111         final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
    112         if (leaf == null) {
    113             return null;
    114         }
    115 
    116         final Certificate[] caList;
    117 
    118         final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid);
    119         if (caBytes != null) {
    120             final Collection<X509Certificate> caChain = toCertificates(caBytes);
    121 
    122             caList = new Certificate[caChain.size() + 1];
    123 
    124             final Iterator<X509Certificate> it = caChain.iterator();
    125             int i = 1;
    126             while (it.hasNext()) {
    127                 caList[i++] = it.next();
    128             }
    129         } else {
    130             caList = new Certificate[1];
    131         }
    132 
    133         caList[0] = leaf;
    134 
    135         return caList;
    136     }
    137 
    138     @Override
    139     public Certificate engineGetCertificate(String alias) {
    140         if (alias == null) {
    141             throw new NullPointerException("alias == null");
    142         }
    143 
    144         byte[] encodedCert = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid);
    145         if (encodedCert != null) {
    146             return getCertificateForPrivateKeyEntry(alias, encodedCert);
    147         }
    148 
    149         encodedCert = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid);
    150         if (encodedCert != null) {
    151             return getCertificateForTrustedCertificateEntry(encodedCert);
    152         }
    153 
    154         // This entry/alias does not contain a certificate.
    155         return null;
    156     }
    157 
    158     private Certificate getCertificateForTrustedCertificateEntry(byte[] encodedCert) {
    159         // For this certificate there shouldn't be a private key in this KeyStore entry. Thus,
    160         // there's no need to wrap this certificate as opposed to the certificate associated with
    161         // a private key entry.
    162         return toCertificate(encodedCert);
    163     }
    164 
    165     private Certificate getCertificateForPrivateKeyEntry(String alias, byte[] encodedCert) {
    166         // All crypto algorithms offered by Android Keystore for its private keys must also
    167         // be offered for the corresponding public keys stored in the Android Keystore. The
    168         // complication is that the underlying keystore service operates only on full key pairs,
    169         // rather than just public keys or private keys. As a result, Android Keystore-backed
    170         // crypto can only be offered for public keys for which keystore contains the
    171         // corresponding private key. This is not the case for certificate-only entries (e.g.,
    172         // trusted certificates).
    173         //
    174         // getCertificate().getPublicKey() is the only way to obtain the public key
    175         // corresponding to the private key stored in the KeyStore. Thus, we need to make sure
    176         // that the returned public key points to the underlying key pair / private key
    177         // when available.
    178 
    179         X509Certificate cert = toCertificate(encodedCert);
    180         if (cert == null) {
    181             // Failed to parse the certificate.
    182             return null;
    183         }
    184 
    185         String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
    186         if (mKeyStore.contains(privateKeyAlias, mUid)) {
    187             // As expected, keystore contains the private key corresponding to this public key. Wrap
    188             // the certificate so that its getPublicKey method returns an Android Keystore
    189             // PublicKey. This key will delegate crypto operations involving this public key to
    190             // Android Keystore when higher-priority providers do not offer these crypto
    191             // operations for this key.
    192             return wrapIntoKeyStoreCertificate(privateKeyAlias, mUid, cert);
    193         } else {
    194             // This KeyStore entry/alias is supposed to contain the private key corresponding to
    195             // the public key in this certificate, but it does not for some reason. It's probably a
    196             // bug. Let other providers handle crypto operations involving the public key returned
    197             // by this certificate's getPublicKey.
    198             return cert;
    199         }
    200     }
    201 
    202     /**
    203      * Wraps the provided cerificate into {@link KeyStoreX509Certificate} so that the public key
    204      * returned by the certificate contains information about the alias of the private key in
    205      * keystore. This is needed so that Android Keystore crypto operations using public keys can
    206      * find out which key alias to use. These operations cannot work without an alias.
    207      */
    208     private static KeyStoreX509Certificate wrapIntoKeyStoreCertificate(
    209             String privateKeyAlias, int uid, X509Certificate certificate) {
    210         return (certificate != null)
    211                 ? new KeyStoreX509Certificate(privateKeyAlias, uid, certificate) : null;
    212     }
    213 
    214     private static X509Certificate toCertificate(byte[] bytes) {
    215         try {
    216             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    217             return (X509Certificate) certFactory.generateCertificate(
    218                     new ByteArrayInputStream(bytes));
    219         } catch (CertificateException e) {
    220             Log.w(NAME, "Couldn't parse certificate in keystore", e);
    221             return null;
    222         }
    223     }
    224 
    225     @SuppressWarnings("unchecked")
    226     private static Collection<X509Certificate> toCertificates(byte[] bytes) {
    227         try {
    228             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    229             return (Collection<X509Certificate>) certFactory.generateCertificates(
    230                             new ByteArrayInputStream(bytes));
    231         } catch (CertificateException e) {
    232             Log.w(NAME, "Couldn't parse certificates in keystore", e);
    233             return new ArrayList<X509Certificate>();
    234         }
    235     }
    236 
    237     private Date getModificationDate(String alias) {
    238         final long epochMillis = mKeyStore.getmtime(alias, mUid);
    239         if (epochMillis == -1L) {
    240             return null;
    241         }
    242 
    243         return new Date(epochMillis);
    244     }
    245 
    246     @Override
    247     public Date engineGetCreationDate(String alias) {
    248         if (alias == null) {
    249             throw new NullPointerException("alias == null");
    250         }
    251 
    252         Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
    253         if (d != null) {
    254             return d;
    255         }
    256 
    257         d = getModificationDate(Credentials.USER_SECRET_KEY + alias);
    258         if (d != null) {
    259             return d;
    260         }
    261 
    262         d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
    263         if (d != null) {
    264             return d;
    265         }
    266 
    267         return getModificationDate(Credentials.CA_CERTIFICATE + alias);
    268     }
    269 
    270     @Override
    271     public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
    272             throws KeyStoreException {
    273         if ((password != null) && (password.length > 0)) {
    274             throw new KeyStoreException("entries cannot be protected with passwords");
    275         }
    276 
    277         if (key instanceof PrivateKey) {
    278             setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
    279         } else if (key instanceof SecretKey) {
    280             setSecretKeyEntry(alias, (SecretKey) key, null);
    281         } else {
    282             throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
    283         }
    284     }
    285 
    286     private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key)
    287             throws KeyStoreException {
    288         String keyAlgorithm = key.getAlgorithm();
    289         KeyProtection.Builder specBuilder;
    290         if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
    291             specBuilder =
    292                     new KeyProtection.Builder(
    293                             KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
    294             // Authorized to be used with any digest (including no digest).
    295             // MD5 was never offered for Android Keystore for ECDSA.
    296             specBuilder.setDigests(
    297                     KeyProperties.DIGEST_NONE,
    298                     KeyProperties.DIGEST_SHA1,
    299                     KeyProperties.DIGEST_SHA224,
    300                     KeyProperties.DIGEST_SHA256,
    301                     KeyProperties.DIGEST_SHA384,
    302                     KeyProperties.DIGEST_SHA512);
    303         } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
    304             specBuilder =
    305                     new KeyProtection.Builder(
    306                             KeyProperties.PURPOSE_ENCRYPT
    307                             | KeyProperties.PURPOSE_DECRYPT
    308                             | KeyProperties.PURPOSE_SIGN
    309                             | KeyProperties.PURPOSE_VERIFY);
    310             // Authorized to be used with any digest (including no digest).
    311             specBuilder.setDigests(
    312                     KeyProperties.DIGEST_NONE,
    313                     KeyProperties.DIGEST_MD5,
    314                     KeyProperties.DIGEST_SHA1,
    315                     KeyProperties.DIGEST_SHA224,
    316                     KeyProperties.DIGEST_SHA256,
    317                     KeyProperties.DIGEST_SHA384,
    318                     KeyProperties.DIGEST_SHA512);
    319             // Authorized to be used with any encryption and signature padding
    320             // schemes (including no padding).
    321             specBuilder.setEncryptionPaddings(
    322                     KeyProperties.ENCRYPTION_PADDING_NONE,
    323                     KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
    324                     KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
    325             specBuilder.setSignaturePaddings(
    326                     KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
    327                     KeyProperties.SIGNATURE_PADDING_RSA_PSS);
    328             // Disable randomized encryption requirement to support encryption
    329             // padding NONE above.
    330             specBuilder.setRandomizedEncryptionRequired(false);
    331         } else {
    332             throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm);
    333         }
    334         specBuilder.setUserAuthenticationRequired(false);
    335 
    336         return specBuilder.build();
    337     }
    338 
    339     private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
    340             java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
    341         int flags = 0;
    342         KeyProtection spec;
    343         if (param == null) {
    344             spec = getLegacyKeyProtectionParameter(key);
    345         } else if (param instanceof KeyStoreParameter) {
    346             spec = getLegacyKeyProtectionParameter(key);
    347             KeyStoreParameter legacySpec = (KeyStoreParameter) param;
    348             if (legacySpec.isEncryptionRequired()) {
    349                 flags = KeyStore.FLAG_ENCRYPTED;
    350             }
    351         } else if (param instanceof KeyProtection) {
    352             spec = (KeyProtection) param;
    353             if (spec.isCriticalToDeviceEncryption()) {
    354                 flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
    355             }
    356 
    357             if (spec.isStrongBoxBacked()) {
    358                 flags |= KeyStore.FLAG_STRONGBOX;
    359             }
    360         } else {
    361             throw new KeyStoreException(
    362                     "Unsupported protection parameter class:" + param.getClass().getName()
    363                     + ". Supported: " + KeyProtection.class.getName() + ", "
    364                     + KeyStoreParameter.class.getName());
    365         }
    366 
    367         // Make sure the chain exists since this is a PrivateKey
    368         if ((chain == null) || (chain.length == 0)) {
    369             throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
    370         }
    371 
    372         // Do chain type checking.
    373         X509Certificate[] x509chain = new X509Certificate[chain.length];
    374         for (int i = 0; i < chain.length; i++) {
    375             if (!"X.509".equals(chain[i].getType())) {
    376                 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
    377                         + i);
    378             }
    379 
    380             if (!(chain[i] instanceof X509Certificate)) {
    381                 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
    382                         + i);
    383             }
    384 
    385             x509chain[i] = (X509Certificate) chain[i];
    386         }
    387 
    388         final byte[] userCertBytes;
    389         try {
    390             userCertBytes = x509chain[0].getEncoded();
    391         } catch (CertificateEncodingException e) {
    392             throw new KeyStoreException("Failed to encode certificate #0", e);
    393         }
    394 
    395         /*
    396          * If we have a chain, store it in the CA certificate slot for this
    397          * alias as concatenated DER-encoded certificates. These can be
    398          * deserialized by {@link CertificateFactory#generateCertificates}.
    399          */
    400         final byte[] chainBytes;
    401         if (chain.length > 1) {
    402             /*
    403              * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
    404              * so we only need the certificates starting at index 1.
    405              */
    406             final byte[][] certsBytes = new byte[x509chain.length - 1][];
    407             int totalCertLength = 0;
    408             for (int i = 0; i < certsBytes.length; i++) {
    409                 try {
    410                     certsBytes[i] = x509chain[i + 1].getEncoded();
    411                     totalCertLength += certsBytes[i].length;
    412                 } catch (CertificateEncodingException e) {
    413                     throw new KeyStoreException("Failed to encode certificate #" + i, e);
    414                 }
    415             }
    416 
    417             /*
    418              * Serialize this into one byte array so we can later call
    419              * CertificateFactory#generateCertificates to recover them.
    420              */
    421             chainBytes = new byte[totalCertLength];
    422             int outputOffset = 0;
    423             for (int i = 0; i < certsBytes.length; i++) {
    424                 final int certLength = certsBytes[i].length;
    425                 System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
    426                 outputOffset += certLength;
    427                 certsBytes[i] = null;
    428             }
    429         } else {
    430             chainBytes = null;
    431         }
    432 
    433         final String pkeyAlias;
    434         if (key instanceof AndroidKeyStorePrivateKey) {
    435             pkeyAlias = ((AndroidKeyStoreKey) key).getAlias();
    436         } else {
    437             pkeyAlias = null;
    438         }
    439 
    440         byte[] pkcs8EncodedPrivateKeyBytes;
    441         KeymasterArguments importArgs;
    442         final boolean shouldReplacePrivateKey;
    443         if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
    444             final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
    445             if (!alias.equals(keySubalias)) {
    446                 throw new KeyStoreException("Can only replace keys with same alias: " + alias
    447                         + " != " + keySubalias);
    448             }
    449             shouldReplacePrivateKey = false;
    450             importArgs = null;
    451             pkcs8EncodedPrivateKeyBytes = null;
    452         } else {
    453             shouldReplacePrivateKey = true;
    454             // Make sure the PrivateKey format is the one we support.
    455             final String keyFormat = key.getFormat();
    456             if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
    457                 throw new KeyStoreException(
    458                         "Unsupported private key export format: " + keyFormat
    459                         + ". Only private keys which export their key material in PKCS#8 format are"
    460                         + " supported.");
    461             }
    462 
    463             // Make sure we can actually encode the key.
    464             pkcs8EncodedPrivateKeyBytes = key.getEncoded();
    465             if (pkcs8EncodedPrivateKeyBytes == null) {
    466                 throw new KeyStoreException("Private key did not export any key material");
    467             }
    468 
    469             importArgs = new KeymasterArguments();
    470             try {
    471                 importArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM,
    472                         KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
    473                                 key.getAlgorithm()));
    474                 @KeyProperties.PurposeEnum int purposes = spec.getPurposes();
    475                 importArgs.addEnums(KeymasterDefs.KM_TAG_PURPOSE,
    476                         KeyProperties.Purpose.allToKeymaster(purposes));
    477                 if (spec.isDigestsSpecified()) {
    478                     importArgs.addEnums(KeymasterDefs.KM_TAG_DIGEST,
    479                             KeyProperties.Digest.allToKeymaster(spec.getDigests()));
    480                 }
    481 
    482                 importArgs.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE,
    483                         KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()));
    484                 int[] keymasterEncryptionPaddings =
    485                         KeyProperties.EncryptionPadding.allToKeymaster(
    486                                 spec.getEncryptionPaddings());
    487                 if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
    488                         && (spec.isRandomizedEncryptionRequired())) {
    489                     for (int keymasterPadding : keymasterEncryptionPaddings) {
    490                         if (!KeymasterUtils
    491                                 .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
    492                                         keymasterPadding)) {
    493                             throw new KeyStoreException(
    494                                     "Randomized encryption (IND-CPA) required but is violated by"
    495                                     + " encryption padding mode: "
    496                                     + KeyProperties.EncryptionPadding.fromKeymaster(
    497                                             keymasterPadding)
    498                                     + ". See KeyProtection documentation.");
    499                         }
    500                     }
    501                 }
    502                 importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings);
    503                 importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING,
    504                         KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings()));
    505                 KeymasterUtils.addUserAuthArgs(importArgs, spec);
    506                 importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
    507                         spec.getKeyValidityStart());
    508                 importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
    509                         spec.getKeyValidityForOriginationEnd());
    510                 importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
    511                         spec.getKeyValidityForConsumptionEnd());
    512             } catch (IllegalArgumentException | IllegalStateException e) {
    513                 throw new KeyStoreException(e);
    514             }
    515         }
    516 
    517 
    518         boolean success = false;
    519         try {
    520             // Store the private key, if necessary
    521             if (shouldReplacePrivateKey) {
    522                 // Delete the stored private key and any related entries before importing the
    523                 // provided key
    524                 Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
    525                 KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
    526                 int errorCode = mKeyStore.importKey(
    527                         Credentials.USER_PRIVATE_KEY + alias,
    528                         importArgs,
    529                         KeymasterDefs.KM_KEY_FORMAT_PKCS8,
    530                         pkcs8EncodedPrivateKeyBytes,
    531                         mUid,
    532                         flags,
    533                         resultingKeyCharacteristics);
    534                 if (errorCode != KeyStore.NO_ERROR) {
    535                     throw new KeyStoreException("Failed to store private key",
    536                             KeyStore.getKeyStoreException(errorCode));
    537                 }
    538             } else {
    539                 // Keep the stored private key around -- delete all other entry types
    540                 Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
    541                 Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
    542             }
    543 
    544             // Store the leaf certificate
    545             int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes,
    546                     mUid, flags);
    547             if (errorCode != KeyStore.NO_ERROR) {
    548                 throw new KeyStoreException("Failed to store certificate #0",
    549                         KeyStore.getKeyStoreException(errorCode));
    550             }
    551 
    552             // Store the certificate chain
    553             errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes,
    554                     mUid, flags);
    555             if (errorCode != KeyStore.NO_ERROR) {
    556                 throw new KeyStoreException("Failed to store certificate chain",
    557                         KeyStore.getKeyStoreException(errorCode));
    558             }
    559             success = true;
    560         } finally {
    561             if (!success) {
    562                 if (shouldReplacePrivateKey) {
    563                     Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
    564                 } else {
    565                     Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
    566                     Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
    567                 }
    568             }
    569         }
    570     }
    571 
    572     private void setSecretKeyEntry(String entryAlias, SecretKey key,
    573             java.security.KeyStore.ProtectionParameter param)
    574             throws KeyStoreException {
    575         if ((param != null) && (!(param instanceof KeyProtection))) {
    576             throw new KeyStoreException(
    577                     "Unsupported protection parameter class: " + param.getClass().getName()
    578                     + ". Supported: " + KeyProtection.class.getName());
    579         }
    580         KeyProtection params = (KeyProtection) param;
    581 
    582         if (key instanceof AndroidKeyStoreSecretKey) {
    583             // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
    584             // overwrite its own entry.
    585             String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias();
    586             if (keyAliasInKeystore == null) {
    587                 throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
    588             }
    589             String keyAliasPrefix = Credentials.USER_PRIVATE_KEY;
    590             if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) {
    591                 // try legacy prefix
    592                 keyAliasPrefix = Credentials.USER_SECRET_KEY;
    593                 if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) {
    594                     throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
    595                             + keyAliasInKeystore);
    596                 }
    597             }
    598             String keyEntryAlias =
    599                     keyAliasInKeystore.substring(keyAliasPrefix.length());
    600             if (!entryAlias.equals(keyEntryAlias)) {
    601                 throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
    602                         + " alias: " + entryAlias + " != " + keyEntryAlias);
    603             }
    604             // This is the entry where this key is already stored. No need to do anything.
    605             if (params != null) {
    606                 throw new KeyStoreException("Modifying KeyStore-backed key using protection"
    607                         + " parameters not supported");
    608             }
    609             return;
    610         }
    611 
    612         if (params == null) {
    613             throw new KeyStoreException(
    614                     "Protection parameters must be specified when importing a symmetric key");
    615         }
    616 
    617         // Not a KeyStore-backed secret key -- import its key material into keystore.
    618         String keyExportFormat = key.getFormat();
    619         if (keyExportFormat == null) {
    620             throw new KeyStoreException(
    621                     "Only secret keys that export their key material are supported");
    622         } else if (!"RAW".equals(keyExportFormat)) {
    623             throw new KeyStoreException(
    624                     "Unsupported secret key material export format: " + keyExportFormat);
    625         }
    626         byte[] keyMaterial = key.getEncoded();
    627         if (keyMaterial == null) {
    628             throw new KeyStoreException("Key did not export its key material despite supporting"
    629                     + " RAW format export");
    630         }
    631 
    632         KeymasterArguments args = new KeymasterArguments();
    633         try {
    634             int keymasterAlgorithm =
    635                     KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(key.getAlgorithm());
    636             args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm);
    637 
    638             int[] keymasterDigests;
    639             if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
    640                 // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
    641                 // implies SHA-256 digest). Because keymaster HMAC key is authorized only for one
    642                 // digest, we don't let import parameters override the digest implied by the key.
    643                 // If the parameters specify digests at all, they must specify only one digest, the
    644                 // only implied by key algorithm.
    645                 int keymasterImpliedDigest =
    646                         KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm());
    647                 if (keymasterImpliedDigest == -1) {
    648                     throw new ProviderException(
    649                             "HMAC key algorithm digest unknown for key algorithm "
    650                                     + key.getAlgorithm());
    651                 }
    652                 keymasterDigests = new int[] {keymasterImpliedDigest};
    653                 if (params.isDigestsSpecified()) {
    654                     // Digest(s) explicitly specified in params -- check that the list consists of
    655                     // exactly one digest, the one implied by key algorithm.
    656                     int[] keymasterDigestsFromParams =
    657                             KeyProperties.Digest.allToKeymaster(params.getDigests());
    658                     if ((keymasterDigestsFromParams.length != 1)
    659                             || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) {
    660                         throw new KeyStoreException(
    661                                 "Unsupported digests specification: "
    662                                 + Arrays.asList(params.getDigests()) + ". Only "
    663                                 + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest)
    664                                 + " supported for HMAC key algorithm " + key.getAlgorithm());
    665                     }
    666                 }
    667             } else {
    668                 // Key algorithm does not imply a digest.
    669                 if (params.isDigestsSpecified()) {
    670                     keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests());
    671                 } else {
    672                     keymasterDigests = EmptyArray.INT;
    673                 }
    674             }
    675             args.addEnums(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests);
    676 
    677             @KeyProperties.PurposeEnum int purposes = params.getPurposes();
    678             int[] keymasterBlockModes =
    679                     KeyProperties.BlockMode.allToKeymaster(params.getBlockModes());
    680             if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
    681                     && (params.isRandomizedEncryptionRequired())) {
    682                 for (int keymasterBlockMode : keymasterBlockModes) {
    683                     if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
    684                             keymasterBlockMode)) {
    685                         throw new KeyStoreException(
    686                                 "Randomized encryption (IND-CPA) required but may be violated by"
    687                                 + " block mode: "
    688                                 + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
    689                                 + ". See KeyProtection documentation.");
    690                     }
    691                 }
    692             }
    693             args.addEnums(KeymasterDefs.KM_TAG_PURPOSE,
    694                     KeyProperties.Purpose.allToKeymaster(purposes));
    695             args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
    696             if (params.getSignaturePaddings().length > 0) {
    697                 throw new KeyStoreException("Signature paddings not supported for symmetric keys");
    698             }
    699             int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
    700                     params.getEncryptionPaddings());
    701             args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
    702             KeymasterUtils.addUserAuthArgs(args, params);
    703             KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
    704                     args,
    705                     keymasterAlgorithm,
    706                     keymasterBlockModes,
    707                     keymasterDigests);
    708             args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
    709                     params.getKeyValidityStart());
    710             args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
    711                     params.getKeyValidityForOriginationEnd());
    712             args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
    713                     params.getKeyValidityForConsumptionEnd());
    714 
    715             if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
    716                     && (!params.isRandomizedEncryptionRequired())) {
    717                 // Permit caller-provided IV when encrypting with this key
    718                 args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
    719             }
    720         } catch (IllegalArgumentException | IllegalStateException e) {
    721             throw new KeyStoreException(e);
    722         }
    723         int flags = 0;
    724         if (params.isCriticalToDeviceEncryption()) {
    725             flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
    726         }
    727         if (params.isStrongBoxBacked()) {
    728             flags |= KeyStore.FLAG_STRONGBOX;
    729         }
    730 
    731         Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias, mUid);
    732         String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + entryAlias;
    733         int errorCode = mKeyStore.importKey(
    734                 keyAliasInKeystore,
    735                 args,
    736                 KeymasterDefs.KM_KEY_FORMAT_RAW,
    737                 keyMaterial,
    738                 mUid,
    739                 flags,
    740                 new KeyCharacteristics());
    741         if (errorCode != KeyStore.NO_ERROR) {
    742             throw new KeyStoreException("Failed to import secret key. Keystore error code: "
    743                 + errorCode);
    744         }
    745     }
    746 
    747     private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry,
    748             java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
    749         if (param != null) {
    750             throw new KeyStoreException("Protection parameters are specified inside wrapped keys");
    751         }
    752 
    753         byte[] maskingKey = new byte[32];
    754 
    755 
    756         KeymasterArguments args = new KeymasterArguments();
    757         String[] parts = entry.getTransformation().split("/");
    758 
    759         String algorithm = parts[0];
    760         if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
    761             args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
    762         } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
    763             args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
    764         }
    765 
    766         if (parts.length > 1) {
    767             String mode = parts[1];
    768             if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(mode)) {
    769                 args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
    770             } else if (KeyProperties.BLOCK_MODE_CBC.equalsIgnoreCase(mode)) {
    771                 args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CBC);
    772             } else if (KeyProperties.BLOCK_MODE_CTR.equalsIgnoreCase(mode)) {
    773                 args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
    774             } else if (KeyProperties.BLOCK_MODE_GCM.equalsIgnoreCase(mode)) {
    775                 args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
    776             }
    777         }
    778 
    779         if (parts.length > 2) {
    780             String padding = parts[2];
    781             if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) {
    782                 // Noop
    783             } else if (KeyProperties.ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) {
    784                 args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
    785             } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) {
    786                 args.addEnums(KeymasterDefs.KM_TAG_PADDING,
    787                     KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT);
    788             } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) {
    789                 args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_OAEP);
    790             }
    791         }
    792 
    793         KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec();
    794         if (spec.isDigestsSpecified()) {
    795             String digest = spec.getDigests()[0];
    796             if (KeyProperties.DIGEST_NONE.equalsIgnoreCase(digest)) {
    797                 // Noop
    798             } else if (KeyProperties.DIGEST_MD5.equalsIgnoreCase(digest)) {
    799                 args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_MD5);
    800             } else if (KeyProperties.DIGEST_SHA1.equalsIgnoreCase(digest)) {
    801                 args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA1);
    802             } else if (KeyProperties.DIGEST_SHA224.equalsIgnoreCase(digest)) {
    803                 args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_224);
    804             } else if (KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)) {
    805                 args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_256);
    806             } else if (KeyProperties.DIGEST_SHA384.equalsIgnoreCase(digest)) {
    807                 args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_384);
    808             } else if (KeyProperties.DIGEST_SHA512.equalsIgnoreCase(digest)) {
    809                 args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_512);
    810             }
    811         }
    812 
    813         int errorCode = mKeyStore.importWrappedKey(
    814             Credentials.USER_SECRET_KEY + alias,
    815             entry.getWrappedKeyBytes(),
    816             Credentials.USER_PRIVATE_KEY + entry.getWrappingKeyAlias(),
    817             maskingKey,
    818             args,
    819             GateKeeper.getSecureUserId(),
    820             0, // FIXME fingerprint id?
    821             mUid,
    822             new KeyCharacteristics());
    823         if (errorCode == KeymasterDefs.KM_ERROR_UNIMPLEMENTED) {
    824           throw new SecureKeyImportUnavailableException("Could not import wrapped key");
    825         } else if (errorCode != KeyStore.NO_ERROR) {
    826             throw new KeyStoreException("Failed to import wrapped key. Keystore error code: "
    827                 + errorCode);
    828         }
    829     }
    830 
    831     @Override
    832     public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
    833             throws KeyStoreException {
    834         throw new KeyStoreException("Operation not supported because key encoding is unknown");
    835     }
    836 
    837     @Override
    838     public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
    839         if (isKeyEntry(alias)) {
    840             throw new KeyStoreException("Entry exists and is not a trusted certificate");
    841         }
    842 
    843         // We can't set something to null.
    844         if (cert == null) {
    845             throw new NullPointerException("cert == null");
    846         }
    847 
    848         final byte[] encoded;
    849         try {
    850             encoded = cert.getEncoded();
    851         } catch (CertificateEncodingException e) {
    852             throw new KeyStoreException(e);
    853         }
    854 
    855         if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, mUid, KeyStore.FLAG_NONE)) {
    856             throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
    857         }
    858     }
    859 
    860     @Override
    861     public void engineDeleteEntry(String alias) throws KeyStoreException {
    862         if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid)) {
    863             throw new KeyStoreException("Failed to delete entry: " + alias);
    864         }
    865     }
    866 
    867     private Set<String> getUniqueAliases() {
    868         final String[] rawAliases = mKeyStore.list("", mUid);
    869         if (rawAliases == null) {
    870             return new HashSet<String>();
    871         }
    872 
    873         final Set<String> aliases = new HashSet<String>(rawAliases.length);
    874         for (String alias : rawAliases) {
    875             final int idx = alias.indexOf('_');
    876             if ((idx == -1) || (alias.length() <= idx)) {
    877                 Log.e(NAME, "invalid alias: " + alias);
    878                 continue;
    879             }
    880 
    881             aliases.add(new String(alias.substring(idx + 1)));
    882         }
    883 
    884         return aliases;
    885     }
    886 
    887     @Override
    888     public Enumeration<String> engineAliases() {
    889         return Collections.enumeration(getUniqueAliases());
    890     }
    891 
    892     @Override
    893     public boolean engineContainsAlias(String alias) {
    894         if (alias == null) {
    895             throw new NullPointerException("alias == null");
    896         }
    897 
    898         return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid)
    899                 || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid)
    900                 || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias, mUid)
    901                 || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid);
    902     }
    903 
    904     @Override
    905     public int engineSize() {
    906         return getUniqueAliases().size();
    907     }
    908 
    909     @Override
    910     public boolean engineIsKeyEntry(String alias) {
    911         return isKeyEntry(alias);
    912     }
    913 
    914     private boolean isKeyEntry(String alias) {
    915         return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) ||
    916                 mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid);
    917     }
    918 
    919 
    920     private boolean isCertificateEntry(String alias) {
    921         if (alias == null) {
    922             throw new NullPointerException("alias == null");
    923         }
    924 
    925         return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid);
    926     }
    927 
    928     @Override
    929     public boolean engineIsCertificateEntry(String alias) {
    930         return !isKeyEntry(alias) && isCertificateEntry(alias);
    931     }
    932 
    933     @Override
    934     public String engineGetCertificateAlias(Certificate cert) {
    935         if (cert == null) {
    936             return null;
    937         }
    938         if (!"X.509".equalsIgnoreCase(cert.getType())) {
    939             // Only X.509 certificates supported
    940             return null;
    941         }
    942         byte[] targetCertBytes;
    943         try {
    944             targetCertBytes = cert.getEncoded();
    945         } catch (CertificateEncodingException e) {
    946             return null;
    947         }
    948         if (targetCertBytes == null) {
    949             return null;
    950         }
    951 
    952         final Set<String> nonCaEntries = new HashSet<String>();
    953 
    954         /*
    955          * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
    956          * says to only compare the first certificate in the chain which is
    957          * equivalent to the USER_CERTIFICATE prefix for the Android keystore
    958          * convention.
    959          */
    960         final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE, mUid);
    961         if (certAliases != null) {
    962             for (String alias : certAliases) {
    963                 final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid);
    964                 if (certBytes == null) {
    965                     continue;
    966                 }
    967 
    968                 nonCaEntries.add(alias);
    969 
    970                 if (Arrays.equals(certBytes, targetCertBytes)) {
    971                     return alias;
    972                 }
    973             }
    974         }
    975 
    976         /*
    977          * Look at all the TrustedCertificateEntry types. Skip all the
    978          * PrivateKeyEntry we looked at above.
    979          */
    980         final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE, mUid);
    981         if (certAliases != null) {
    982             for (String alias : caAliases) {
    983                 if (nonCaEntries.contains(alias)) {
    984                     continue;
    985                 }
    986 
    987                 final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid);
    988                 if (certBytes == null) {
    989                     continue;
    990                 }
    991 
    992                 if (Arrays.equals(certBytes, targetCertBytes)) {
    993                     return alias;
    994                 }
    995             }
    996         }
    997 
    998         return null;
    999     }
   1000 
   1001     @Override
   1002     public void engineStore(OutputStream stream, char[] password) throws IOException,
   1003             NoSuchAlgorithmException, CertificateException {
   1004         throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
   1005     }
   1006 
   1007     @Override
   1008     public void engineLoad(InputStream stream, char[] password) throws IOException,
   1009             NoSuchAlgorithmException, CertificateException {
   1010         if (stream != null) {
   1011             throw new IllegalArgumentException("InputStream not supported");
   1012         }
   1013 
   1014         if (password != null) {
   1015             throw new IllegalArgumentException("password not supported");
   1016         }
   1017 
   1018         // Unfortunate name collision.
   1019         mKeyStore = KeyStore.getInstance();
   1020         mUid = KeyStore.UID_SELF;
   1021     }
   1022 
   1023     @Override
   1024     public void engineLoad(LoadStoreParameter param) throws IOException,
   1025             NoSuchAlgorithmException, CertificateException {
   1026         int uid = KeyStore.UID_SELF;
   1027         if (param != null) {
   1028             if (param instanceof AndroidKeyStoreLoadStoreParameter) {
   1029                 uid = ((AndroidKeyStoreLoadStoreParameter) param).getUid();
   1030             } else {
   1031                 throw new IllegalArgumentException(
   1032                         "Unsupported param type: " + param.getClass());
   1033             }
   1034         }
   1035         mKeyStore = KeyStore.getInstance();
   1036         mUid = uid;
   1037     }
   1038 
   1039     @Override
   1040     public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
   1041             throws KeyStoreException {
   1042         if (entry == null) {
   1043             throw new KeyStoreException("entry == null");
   1044         }
   1045 
   1046         Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
   1047 
   1048         if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) {
   1049             java.security.KeyStore.TrustedCertificateEntry trE =
   1050                     (java.security.KeyStore.TrustedCertificateEntry) entry;
   1051             engineSetCertificateEntry(alias, trE.getTrustedCertificate());
   1052             return;
   1053         }
   1054 
   1055         if (entry instanceof PrivateKeyEntry) {
   1056             PrivateKeyEntry prE = (PrivateKeyEntry) entry;
   1057             setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param);
   1058         } else if (entry instanceof SecretKeyEntry) {
   1059             SecretKeyEntry secE = (SecretKeyEntry) entry;
   1060             setSecretKeyEntry(alias, secE.getSecretKey(), param);
   1061         } else if (entry instanceof WrappedKeyEntry) {
   1062             WrappedKeyEntry wke = (WrappedKeyEntry) entry;
   1063             setWrappedKeyEntry(alias, wke, param);
   1064         } else {
   1065             throw new KeyStoreException(
   1066                     "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
   1067                     + "; was " + entry);
   1068         }
   1069     }
   1070 
   1071     /**
   1072      * {@link X509Certificate} which returns {@link AndroidKeyStorePublicKey} from
   1073      * {@link #getPublicKey()}. This is so that crypto operations on these public keys contain
   1074      * can find out which keystore private key entry to use. This is needed so that Android Keystore
   1075      * crypto operations using public keys can find out which key alias to use. These operations
   1076      * require an alias.
   1077      */
   1078     static class KeyStoreX509Certificate extends DelegatingX509Certificate {
   1079         private final String mPrivateKeyAlias;
   1080         private final int mPrivateKeyUid;
   1081         KeyStoreX509Certificate(String privateKeyAlias, int privateKeyUid,
   1082                 X509Certificate delegate) {
   1083             super(delegate);
   1084             mPrivateKeyAlias = privateKeyAlias;
   1085             mPrivateKeyUid = privateKeyUid;
   1086         }
   1087 
   1088         @Override
   1089         public PublicKey getPublicKey() {
   1090             PublicKey original = super.getPublicKey();
   1091             return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
   1092                     mPrivateKeyAlias, mPrivateKeyUid,
   1093                     original.getAlgorithm(), original.getEncoded());
   1094         }
   1095     }
   1096 }
   1097