content: /*
   * Copyright (C) 2012 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.security.keystore;
  
  import android.security.Credentials;
  import android.security.GateKeeper;
  import android.security.KeyStore;
  import android.security.KeyStoreParameter;
  import android.security.keymaster.KeyCharacteristics;
  import android.security.keymaster.KeymasterArguments;
  import android.security.keymaster.KeymasterDefs;
  import android.security.keystore.KeyPermanentlyInvalidatedException;
  import android.security.keystore.KeyProperties;
  import android.security.keystore.KeyProtection;
  import android.security.keystore.SecureKeyImportUnavailableException;
  import android.security.keystore.WrappedKeyEntry;
  import android.util.Log;
  
  import libcore.util.EmptyArray;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.security.Key;
  import java.security.KeyStore.Entry;
  import java.security.KeyStore.LoadStoreParameter;
  import java.security.KeyStore.PrivateKeyEntry;
  import java.security.KeyStore.ProtectionParameter;
  import java.security.KeyStore.SecretKeyEntry;
  import java.security.KeyStoreException;
  import java.security.KeyStoreSpi;
  import java.security.NoSuchAlgorithmException;
  import java.security.PrivateKey;
  import java.security.ProviderException;
  import java.security.PublicKey;
  import java.security.UnrecoverableKeyException;
  import java.security.cert.Certificate;
  import java.security.cert.CertificateEncodingException;
  import java.security.cert.CertificateException;
  import java.security.cert.CertificateFactory;
  import java.security.cert.X509Certificate;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Date;
  import java.util.Enumeration;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Set;
  
  import javax.crypto.SecretKey;
  
  /**
   * A java.security.KeyStore interface for the Android KeyStore. An instance of
   * it can be created via the {@link java.security.KeyStore#getInstance(String)
   * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
   * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
   * <p>
   * This is built on top of Android's keystore daemon. The convention of alias
   * use is:
   * <p>
   * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
   * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
   * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
   * entry which will have the rest of the chain concatenated in BER format.
   * <p>
   * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
   * with a single certificate.
   *
   * @hide
   */
  public class AndroidKeyStoreSpi extends KeyStoreSpi {
      public static final String NAME = "AndroidKeyStore";
  
      private KeyStore mKeyStore;
      private int mUid = KeyStore.UID_SELF;
  
      @Override
      public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
              UnrecoverableKeyException {
          String userKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
          AndroidKeyStoreKey key;
          if (!mKeyStore.contains(userKeyAlias, mUid)) {
              // try legacy prefix for backward compatibility
              userKeyAlias = Credentials.USER_SECRET_KEY + alias;
              if (!mKeyStore.contains(userKeyAlias, mUid)) return null;
          }
          try {
              key = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore,
                                                                               userKeyAlias,
                                                                               mUid);
          } catch (KeyPermanentlyInvalidatedException e) {
              throw new UnrecoverableKeyException(e.getMessage());
          }
          return key;
      }
  
      @Override
      public Certificate[] engineGetCertificateChain(String alias) {
          if (alias == null) {
              throw new NullPointerException("alias == null");
          }
  
          final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
          if (leaf == null) {
              return null;
          }
  
          final Certificate[] caList;
  
          // Suppress the key not found warning for this call. It seems that this error is exclusively
          // being thrown when there is a self signed certificate chain, so when the keystore service
          // attempts to query for the CA details, it obviously fails to find them and returns a
          // key not found exception. This is WAI, and throwing a stack trace here can be very
          // misleading since the trace is not clear.
          final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias,
                                               mUid,
                                               true /* suppressKeyNotFoundWarning */);
          if (caBytes != null) {
              final Collection<X509Certificate> caChain = toCertificates(caBytes);
  
              caList = new Certificate[caChain.size() + 1];
  
              final Iterator<X509Certificate> it = caChain.iterator();
              int i = 1;
              while (it.hasNext()) {
                  caList[i++] = it.next();
              }
          } else {
              caList = new Certificate[1];
          }
  
          caList[0] = leaf;
  
          return caList;
      }
  
      @Override
      public Certificate engineGetCertificate(String alias) {
          if (alias == null) {
              throw new NullPointerException("alias == null");
          }
  
          byte[] encodedCert = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid);
          if (encodedCert != null) {
              return getCertificateForPrivateKeyEntry(alias, encodedCert);
          }
  
          encodedCert = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid);
          if (encodedCert != null) {
              return getCertificateForTrustedCertificateEntry(encodedCert);
          }
  
          // This entry/alias does not contain a certificate.
          return null;
      }
  
      private Certificate getCertificateForTrustedCertificateEntry(byte[] encodedCert) {
          // For this certificate there shouldn't be a private key in this KeyStore entry. Thus,
          // there's no need to wrap this certificate as opposed to the certificate associated with
          // a private key entry.
          return toCertificate(encodedCert);
      }
  
      private Certificate getCertificateForPrivateKeyEntry(String alias, byte[] encodedCert) {
          // All crypto algorithms offered by Android Keystore for its private keys must also
          // be offered for the corresponding public keys stored in the Android Keystore. The
          // complication is that the underlying keystore service operates only on full key pairs,
          // rather than just public keys or private keys. As a result, Android Keystore-backed
          // crypto can only be offered for public keys for which keystore contains the
          // corresponding private key. This is not the case for certificate-only entries (e.g.,
          // trusted certificates).
          //
          // getCertificate().getPublicKey() is the only way to obtain the public key
          // corresponding to the private key stored in the KeyStore. Thus, we need to make sure
          // that the returned public key points to the underlying key pair / private key
          // when available.
  
          X509Certificate cert = toCertificate(encodedCert);
          if (cert == null) {
              // Failed to parse the certificate.
              return null;
          }
  
          String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
          if (mKeyStore.contains(privateKeyAlias, mUid)) {
              // As expected, keystore contains the private key corresponding to this public key. Wrap
              // the certificate so that its getPublicKey method returns an Android Keystore
              // PublicKey. This key will delegate crypto operations involving this public key to
              // Android Keystore when higher-priority providers do not offer these crypto
              // operations for this key.
              return wrapIntoKeyStoreCertificate(privateKeyAlias, mUid, cert);
          } else {
              // This KeyStore entry/alias is supposed to contain the private key corresponding to
              // the public key in this certificate, but it does not for some reason. It's probably a
              // bug. Let other providers handle crypto operations involving the public key returned
              // by this certificate's getPublicKey.
              return cert;
          }
      }
  
      /**
       * Wraps the provided cerificate into {@link KeyStoreX509Certificate} so that the public key
       * returned by the certificate contains information about the alias of the private key in
       * keystore. This is needed so that Android Keystore crypto operations using public keys can
       * find out which key alias to use. These operations cannot work without an alias.
       */
      private static KeyStoreX509Certificate wrapIntoKeyStoreCertificate(
              String privateKeyAlias, int uid, X509Certificate certificate) {
          return (certificate != null)
                  ? new KeyStoreX509Certificate(privateKeyAlias, uid, certificate) : null;
      }
  
      private static X509Certificate toCertificate(byte[] bytes) {
          try {
              final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
              return (X509Certificate) certFactory.generateCertificate(
                      new ByteArrayInputStream(bytes));
          } catch (CertificateException e) {
              Log.w(NAME, "Couldn't parse certificate in keystore", e);
              return null;
          }
      }
  
      @SuppressWarnings("unchecked")
      private static Collection<X509Certificate> toCertificates(byte[] bytes) {
          try {
              final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
              return (Collection<X509Certificate>) certFactory.generateCertificates(
                              new ByteArrayInputStream(bytes));
          } catch (CertificateException e) {
              Log.w(NAME, "Couldn't parse certificates in keystore", e);
              return new ArrayList<X509Certificate>();
          }
      }
  
      private Date getModificationDate(String alias) {
          final long epochMillis = mKeyStore.getmtime(alias, mUid);
          if (epochMillis == -1L) {
              return null;
          }
  
          return new Date(epochMillis);
      }
  
      @Override
      public Date engineGetCreationDate(String alias) {
          if (alias == null) {
              throw new NullPointerException("alias == null");
          }
  
          Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
          if (d != null) {
              return d;
          }
  
          d = getModificationDate(Credentials.USER_SECRET_KEY + alias);
          if (d != null) {
              return d;
          }
  
          d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
          if (d != null) {
              return d;
          }
  
          return getModificationDate(Credentials.CA_CERTIFICATE + alias);
      }
  
      @Override
      public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
              throws KeyStoreException {
          if ((password != null) && (password.length > 0)) {
              throw new KeyStoreException("entries cannot be protected with passwords");
          }
  
          if (key instanceof PrivateKey) {
              setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
          } else if (key instanceof SecretKey) {
              setSecretKeyEntry(alias, (SecretKey) key, null);
          } else {
              throw new KeyStoreException("Only PrivateKey and SecretKey are supported");
          }
      }
  
      private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key)
              throws KeyStoreException {
          String keyAlgorithm = key.getAlgorithm();
          KeyProtection.Builder specBuilder;
          if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
              specBuilder =
                      new KeyProtection.Builder(
                              KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
              // Authorized to be used with any digest (including no digest).
              // MD5 was never offered for Android Keystore for ECDSA.
              specBuilder.setDigests(
                      KeyProperties.DIGEST_NONE,
                      KeyProperties.DIGEST_SHA1,
                      KeyProperties.DIGEST_SHA224,
                      KeyProperties.DIGEST_SHA256,
                      KeyProperties.DIGEST_SHA384,
                      KeyProperties.DIGEST_SHA512);
          } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
              specBuilder =
                      new KeyProtection.Builder(
                              KeyProperties.PURPOSE_ENCRYPT
                              | KeyProperties.PURPOSE_DECRYPT
                              | KeyProperties.PURPOSE_SIGN
                              | KeyProperties.PURPOSE_VERIFY);
              // Authorized to be used with any digest (including no digest).
              specBuilder.setDigests(
                      KeyProperties.DIGEST_NONE,
                      KeyProperties.DIGEST_MD5,
                      KeyProperties.DIGEST_SHA1,
                      KeyProperties.DIGEST_SHA224,
                      KeyProperties.DIGEST_SHA256,
                      KeyProperties.DIGEST_SHA384,
                      KeyProperties.DIGEST_SHA512);
              // Authorized to be used with any encryption and signature padding
              // schemes (including no padding).
              specBuilder.setEncryptionPaddings(
                      KeyProperties.ENCRYPTION_PADDING_NONE,
                      KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
                      KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
              specBuilder.setSignaturePaddings(
                      KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
                      KeyProperties.SIGNATURE_PADDING_RSA_PSS);
              // Disable randomized encryption requirement to support encryption
              // padding NONE above.
              specBuilder.setRandomizedEncryptionRequired(false);
          } else {
              throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm);
          }
          specBuilder.setUserAuthenticationRequired(false);
  
          return specBuilder.build();
      }
  
      private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
              java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
          int flags = 0;
          KeyProtection spec;
          if (param == null) {
              spec = getLegacyKeyProtectionParameter(key);
          } else if (param instanceof KeyStoreParameter) {
              spec = getLegacyKeyProtectionParameter(key);
              KeyStoreParameter legacySpec = (KeyStoreParameter) param;
              if (legacySpec.isEncryptionRequired()) {
                  flags = KeyStore.FLAG_ENCRYPTED;
              }
          } else if (param instanceof KeyProtection) {
              spec = (KeyProtection) param;
              if (spec.isCriticalToDeviceEncryption()) {
                  flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
              }
  
              if (spec.isStrongBoxBacked()) {
                  flags |= KeyStore.FLAG_STRONGBOX;
              }
          } else {
              throw new KeyStoreException(
                      "Unsupported protection parameter class:" + param.getClass().getName()
                      + ". Supported: " + KeyProtection.class.getName() + ", "
                      + KeyStoreParameter.class.getName());
          }
  
          // Make sure the chain exists since this is a PrivateKey
          if ((chain == null) || (chain.length == 0)) {
              throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
          }
  
          // Do chain type checking.
          X509Certificate[] x509chain = new X509Certificate[chain.length];
          for (int i = 0; i < chain.length; i++) {
              if (!"X.509".equals(chain[i].getType())) {
                  throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
                          + i);
              }
  
              if (!(chain[i] instanceof X509Certificate)) {
                  throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
                          + i);
              }
  
              x509chain[i] = (X509Certificate) chain[i];
          }
  
          final byte[] userCertBytes;
          try {
              userCertBytes = x509chain[0].getEncoded();
          } catch (CertificateEncodingException e) {
              throw new KeyStoreException("Failed to encode certificate #0", e);
          }
  
          /*
           * If we have a chain, store it in the CA certificate slot for this
           * alias as concatenated DER-encoded certificates. These can be
           * deserialized by {@link CertificateFactory#generateCertificates}.
           */
          final byte[] chainBytes;
          if (chain.length > 1) {
              /*
               * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
               * so we only need the certificates starting at index 1.
               */
              final byte[][] certsBytes = new byte[x509chain.length - 1][];
              int totalCertLength = 0;
              for (int i = 0; i < certsBytes.length; i++) {
                  try {
                      certsBytes[i] = x509chain[i + 1].getEncoded();
                      totalCertLength += certsBytes[i].length;
                  } catch (CertificateEncodingException e) {
                      throw new KeyStoreException("Failed to encode certificate #" + i, e);
                  }
              }
  
              /*
               * Serialize this into one byte array so we can later call
               * CertificateFactory#generateCertificates to recover them.
               */
              chainBytes = new byte[totalCertLength];
              int outputOffset = 0;
              for (int i = 0; i < certsBytes.length; i++) {
                  final int certLength = certsBytes[i].length;
                  System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
                  outputOffset += certLength;
                  certsBytes[i] = null;
              }
          } else {
              chainBytes = null;
          }
  
          final String pkeyAlias;
          if (key instanceof AndroidKeyStorePrivateKey) {
              pkeyAlias = ((AndroidKeyStoreKey) key).getAlias();
          } else {
              pkeyAlias = null;
          }
  
          byte[] pkcs8EncodedPrivateKeyBytes;
          KeymasterArguments importArgs;
          final boolean shouldReplacePrivateKey;
          if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
              final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
              if (!alias.equals(keySubalias)) {
                  throw new KeyStoreException("Can only replace keys with same alias: " + alias
                          + " != " + keySubalias);
              }
              shouldReplacePrivateKey = false;
              importArgs = null;
              pkcs8EncodedPrivateKeyBytes = null;
          } else {
              shouldReplacePrivateKey = true;
              // Make sure the PrivateKey format is the one we support.
              final String keyFormat = key.getFormat();
              if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
                  throw new KeyStoreException(
                          "Unsupported private key export format: " + keyFormat
                          + ". Only private keys which export their key material in PKCS#8 format are"
                          + " supported.");
              }
  
              // Make sure we can actually encode the key.
              pkcs8EncodedPrivateKeyBytes = key.getEncoded();
              if (pkcs8EncodedPrivateKeyBytes == null) {
                  throw new KeyStoreException("Private key did not export any key material");
              }
  
              importArgs = new KeymasterArguments();
              try {
                  importArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM,
                          KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
                                  key.getAlgorithm()));
                  @KeyProperties.PurposeEnum int purposes = spec.getPurposes();
                  importArgs.addEnums(KeymasterDefs.KM_TAG_PURPOSE,
                          KeyProperties.Purpose.allToKeymaster(purposes));
                  if (spec.isDigestsSpecified()) {
                      importArgs.addEnums(KeymasterDefs.KM_TAG_DIGEST,
                              KeyProperties.Digest.allToKeymaster(spec.getDigests()));
                  }
  
                  importArgs.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE,
                          KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()));
                  int[] keymasterEncryptionPaddings =
                          KeyProperties.EncryptionPadding.allToKeymaster(
                                  spec.getEncryptionPaddings());
                  if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
                          && (spec.isRandomizedEncryptionRequired())) {
                      for (int keymasterPadding : keymasterEncryptionPaddings) {
                          if (!KeymasterUtils
                                  .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
                                          keymasterPadding)) {
                              throw new KeyStoreException(
                                      "Randomized encryption (IND-CPA) required but is violated by"
                                      + " encryption padding mode: "
                                      + KeyProperties.EncryptionPadding.fromKeymaster(
                                              keymasterPadding)
                                      + ". See KeyProtection documentation.");
                          }
                      }
                  }
                  importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings);
                  importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING,
                          KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings()));
                  KeymasterUtils.addUserAuthArgs(importArgs, spec);
                  importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
                          spec.getKeyValidityStart());
                  importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
                          spec.getKeyValidityForOriginationEnd());
                  importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
                          spec.getKeyValidityForConsumptionEnd());
              } catch (IllegalArgumentException | IllegalStateException e) {
                  throw new KeyStoreException(e);
              }
          }
  
  
          boolean success = false;
          try {
              // Store the private key, if necessary
              if (shouldReplacePrivateKey) {
                  // Delete the stored private key and any related entries before importing the
                  // provided key
                  Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
                  KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
                  int errorCode = mKeyStore.importKey(
                          Credentials.USER_PRIVATE_KEY + alias,
                          importArgs,
                          KeymasterDefs.KM_KEY_FORMAT_PKCS8,
                          pkcs8EncodedPrivateKeyBytes,
                          mUid,
                          flags,
                          resultingKeyCharacteristics);
                  if (errorCode != KeyStore.NO_ERROR) {
                      throw new KeyStoreException("Failed to store private key",
                              KeyStore.getKeyStoreException(errorCode));
                  }
              } else {
                  // Keep the stored private key around -- delete all other entry types
                  Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
                  Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
              }
  
              // Store the leaf certificate
              int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes,
                      mUid, flags);
              if (errorCode != KeyStore.NO_ERROR) {
                  throw new KeyStoreException("Failed to store certificate #0",
                          KeyStore.getKeyStoreException(errorCode));
              }
  
              // Store the certificate chain
              errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes,
                      mUid, flags);
              if (errorCode != KeyStore.NO_ERROR) {
                  throw new KeyStoreException("Failed to store certificate chain",
                          KeyStore.getKeyStoreException(errorCode));
              }
              success = true;
          } finally {
              if (!success) {
                  if (shouldReplacePrivateKey) {
                      Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
                  } else {
                      Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
                      Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
                  }
              }
          }
      }
  
      private void setSecretKeyEntry(String entryAlias, SecretKey key,
              java.security.KeyStore.ProtectionParameter param)
              throws KeyStoreException {
          if ((param != null) && (!(param instanceof KeyProtection))) {
              throw new KeyStoreException(
                      "Unsupported protection parameter class: " + param.getClass().getName()
                      + ". Supported: " + KeyProtection.class.getName());
          }
          KeyProtection params = (KeyProtection) param;
  
          if (key instanceof AndroidKeyStoreSecretKey) {
              // KeyStore-backed secret key. It cannot be duplicated into another entry and cannot
              // overwrite its own entry.
              String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias();
              if (keyAliasInKeystore == null) {
                  throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
              }
              String keyAliasPrefix = Credentials.USER_PRIVATE_KEY;
              if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) {
                  // try legacy prefix
                  keyAliasPrefix = Credentials.USER_SECRET_KEY;
                  if (!keyAliasInKeystore.startsWith(keyAliasPrefix)) {
                      throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
                              + keyAliasInKeystore);
                  }
              }
              String keyEntryAlias =
                      keyAliasInKeystore.substring(keyAliasPrefix.length());
              if (!entryAlias.equals(keyEntryAlias)) {
                  throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
                          + " alias: " + entryAlias + " != " + keyEntryAlias);
              }
              // This is the entry where this key is already stored. No need to do anything.
              if (params != null) {
                  throw new KeyStoreException("Modifying KeyStore-backed key using protection"
                          + " parameters not supported");
              }
              return;
          }
  
          if (params == null) {
              throw new KeyStoreException(
                      "Protection parameters must be specified when importing a symmetric key");
          }
  
          // Not a KeyStore-backed secret key -- import its key material into keystore.
          String keyExportFormat = key.getFormat();
          if (keyExportFormat == null) {
              throw new KeyStoreException(
                      "Only secret keys that export their key material are supported");
          } else if (!"RAW".equals(keyExportFormat)) {
              throw new KeyStoreException(
                      "Unsupported secret key material export format: " + keyExportFormat);
          }
          byte[] keyMaterial = key.getEncoded();
          if (keyMaterial == null) {
              throw new KeyStoreException("Key did not export its key material despite supporting"
                      + " RAW format export");
          }
  
          KeymasterArguments args = new KeymasterArguments();
          try {
              int keymasterAlgorithm =
                      KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm(key.getAlgorithm());
              args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keymasterAlgorithm);
  
              int[] keymasterDigests;
              if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
                  // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
                  // implies SHA-256 digest). Because keymaster HMAC key is authorized only for one
                  // digest, we don't let import parameters override the digest implied by the key.
                  // If the parameters specify digests at all, they must specify only one digest, the
                  // only implied by key algorithm.
                  int keymasterImpliedDigest =
                          KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm());
                  if (keymasterImpliedDigest == -1) {
                      throw new ProviderException(
                              "HMAC key algorithm digest unknown for key algorithm "
                                      + key.getAlgorithm());
                  }
                  keymasterDigests = new int[] {keymasterImpliedDigest};
                  if (params.isDigestsSpecified()) {
                      // Digest(s) explicitly specified in params -- check that the list consists of
                      // exactly one digest, the one implied by key algorithm.
                      int[] keymasterDigestsFromParams =
                              KeyProperties.Digest.allToKeymaster(params.getDigests());
                      if ((keymasterDigestsFromParams.length != 1)
                              || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) {
                          throw new KeyStoreException(
                                  "Unsupported digests specification: "
                                  + Arrays.asList(params.getDigests()) + ". Only "
                                  + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest)
                                  + " supported for HMAC key algorithm " + key.getAlgorithm());
                      }
                  }
              } else {
                  // Key algorithm does not imply a digest.
                  if (params.isDigestsSpecified()) {
                      keymasterDigests = KeyProperties.Digest.allToKeymaster(params.getDigests());
                  } else {
                      keymasterDigests = EmptyArray.INT;
                  }
              }
              args.addEnums(KeymasterDefs.KM_TAG_DIGEST, keymasterDigests);
  
              @KeyProperties.PurposeEnum int purposes = params.getPurposes();
              int[] keymasterBlockModes =
                      KeyProperties.BlockMode.allToKeymaster(params.getBlockModes());
              if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
                      && (params.isRandomizedEncryptionRequired())) {
                  for (int keymasterBlockMode : keymasterBlockModes) {
                      if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
                              keymasterBlockMode)) {
                          throw new KeyStoreException(
                                  "Randomized encryption (IND-CPA) required but may be violated by"
                                  + " block mode: "
                                  + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
                                  + ". See KeyProtection documentation.");
                      }
                  }
              }
              args.addEnums(KeymasterDefs.KM_TAG_PURPOSE,
                      KeyProperties.Purpose.allToKeymaster(purposes));
              args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
              if (params.getSignaturePaddings().length > 0) {
                  throw new KeyStoreException("Signature paddings not supported for symmetric keys");
              }
              int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
                      params.getEncryptionPaddings());
              args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
              KeymasterUtils.addUserAuthArgs(args, params);
              KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
                      args,
                      keymasterAlgorithm,
                      keymasterBlockModes,
                      keymasterDigests);
              args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
                      params.getKeyValidityStart());
              args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
                      params.getKeyValidityForOriginationEnd());
              args.addDateIfNotNull(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
                      params.getKeyValidityForConsumptionEnd());
  
              if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
                      && (!params.isRandomizedEncryptionRequired())) {
                  // Permit caller-provided IV when encrypting with this key
                  args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
              }
          } catch (IllegalArgumentException | IllegalStateException e) {
              throw new KeyStoreException(e);
          }
          int flags = 0;
          if (params.isCriticalToDeviceEncryption()) {
              flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
          }
          if (params.isStrongBoxBacked()) {
              flags |= KeyStore.FLAG_STRONGBOX;
          }
  
          Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias, mUid);
          String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + entryAlias;
          int errorCode = mKeyStore.importKey(
                  keyAliasInKeystore,
                  args,
                  KeymasterDefs.KM_KEY_FORMAT_RAW,
                  keyMaterial,
                  mUid,
                  flags,
                  new KeyCharacteristics());
          if (errorCode != KeyStore.NO_ERROR) {
              throw new KeyStoreException("Failed to import secret key. Keystore error code: "
                  + errorCode);
          }
      }
  
      private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry,
              java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
          if (param != null) {
              throw new KeyStoreException("Protection parameters are specified inside wrapped keys");
          }
  
          byte[] maskingKey = new byte[32];
  
  
          KeymasterArguments args = new KeymasterArguments();
          String[] parts = entry.getTransformation().split("/");
  
          String algorithm = parts[0];
          if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
              args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
          } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
              args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
          }
  
          if (parts.length > 1) {
              String mode = parts[1];
              if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(mode)) {
                  args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
              } else if (KeyProperties.BLOCK_MODE_CBC.equalsIgnoreCase(mode)) {
                  args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CBC);
              } else if (KeyProperties.BLOCK_MODE_CTR.equalsIgnoreCase(mode)) {
                  args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
              } else if (KeyProperties.BLOCK_MODE_GCM.equalsIgnoreCase(mode)) {
                  args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
              }
          }
  
          if (parts.length > 2) {
              String padding = parts[2];
              if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) {
                  // Noop
              } else if (KeyProperties.ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) {
                  args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
              } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) {
                  args.addEnums(KeymasterDefs.KM_TAG_PADDING,
                      KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT);
              } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) {
                  args.addEnums(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_OAEP);
              }
          }
  
          KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec();
          if (spec.isDigestsSpecified()) {
              String digest = spec.getDigests()[0];
              if (KeyProperties.DIGEST_NONE.equalsIgnoreCase(digest)) {
                  // Noop
              } else if (KeyProperties.DIGEST_MD5.equalsIgnoreCase(digest)) {
                  args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_MD5);
              } else if (KeyProperties.DIGEST_SHA1.equalsIgnoreCase(digest)) {
                  args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA1);
              } else if (KeyProperties.DIGEST_SHA224.equalsIgnoreCase(digest)) {
                  args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_224);
              } else if (KeyProperties.DIGEST_SHA256.equalsIgnoreCase(digest)) {
                  args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_256);
              } else if (KeyProperties.DIGEST_SHA384.equalsIgnoreCase(digest)) {
                  args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_384);
              } else if (KeyProperties.DIGEST_SHA512.equalsIgnoreCase(digest)) {
                  args.addEnums(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_512);
              }
          }
  
          int errorCode = mKeyStore.importWrappedKey(
              Credentials.USER_PRIVATE_KEY + alias,
              entry.getWrappedKeyBytes(),
              Credentials.USER_PRIVATE_KEY + entry.getWrappingKeyAlias(),
              maskingKey,
              args,
              GateKeeper.getSecureUserId(),
              0, // FIXME fingerprint id?
              mUid,
              new KeyCharacteristics());
          if (errorCode == KeymasterDefs.KM_ERROR_UNIMPLEMENTED) {
            throw new SecureKeyImportUnavailableException("Could not import wrapped key");
          } else if (errorCode != KeyStore.NO_ERROR) {
              throw new KeyStoreException("Failed to import wrapped key. Keystore error code: "
                  + errorCode);
          }
      }
  
      @Override
      public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
              throws KeyStoreException {
          throw new KeyStoreException("Operation not supported because key encoding is unknown");
      }
  
      @Override
      public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
          if (isKeyEntry(alias)) {
              throw new KeyStoreException("Entry exists and is not a trusted certificate");
          }
  
          // We can't set something to null.
          if (cert == null) {
              throw new NullPointerException("cert == null");
          }
  
          final byte[] encoded;
          try {
              encoded = cert.getEncoded();
          } catch (CertificateEncodingException e) {
              throw new KeyStoreException(e);
          }
  
          if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded, mUid, KeyStore.FLAG_NONE)) {
              throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
          }
      }
  
      @Override
      public void engineDeleteEntry(String alias) throws KeyStoreException {
          if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid)) {
              throw new KeyStoreException("Failed to delete entry: " + alias);
          }
      }
  
      private Set<String> getUniqueAliases() {
          final String[] rawAliases = mKeyStore.list("", mUid);
          if (rawAliases == null) {
              return new HashSet<String>();
          }
  
          final Set<String> aliases = new HashSet<String>(rawAliases.length);
          for (String alias : rawAliases) {
              final int idx = alias.indexOf('_');
              if ((idx == -1) || (alias.length() <= idx)) {
                  Log.e(NAME, "invalid alias: " + alias);
                  continue;
              }
  
              aliases.add(new String(alias.substring(idx + 1)));
          }
  
          return aliases;
      }
  
      @Override
      public Enumeration<String> engineAliases() {
          return Collections.enumeration(getUniqueAliases());
      }
  
      @Override
      public boolean engineContainsAlias(String alias) {
          if (alias == null) {
              throw new NullPointerException("alias == null");
          }
  
          return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid)
                  || mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid)
                  || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias, mUid)
                  || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid);
      }
  
      @Override
      public int engineSize() {
          return getUniqueAliases().size();
      }
  
      @Override
      public boolean engineIsKeyEntry(String alias) {
          return isKeyEntry(alias);
      }
  
      private boolean isKeyEntry(String alias) {
          return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) ||
                  mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid);
      }
  
  
      private boolean isCertificateEntry(String alias) {
          if (alias == null) {
              throw new NullPointerException("alias == null");
          }
  
          return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias, mUid);
      }
  
      @Override
      public boolean engineIsCertificateEntry(String alias) {
          return !isKeyEntry(alias) && isCertificateEntry(alias);
      }
  
      @Override
      public String engineGetCertificateAlias(Certificate cert) {
          if (cert == null) {
              return null;
          }
          if (!"X.509".equalsIgnoreCase(cert.getType())) {
              // Only X.509 certificates supported
              return null;
          }
          byte[] targetCertBytes;
          try {
              targetCertBytes = cert.getEncoded();
          } catch (CertificateEncodingException e) {
              return null;
          }
          if (targetCertBytes == null) {
              return null;
          }
  
          final Set<String> nonCaEntries = new HashSet<String>();
  
          /*
           * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
           * says to only compare the first certificate in the chain which is
           * equivalent to the USER_CERTIFICATE prefix for the Android keystore
           * convention.
           */
          final String[] certAliases = mKeyStore.list(Credentials.USER_CERTIFICATE, mUid);
          if (certAliases != null) {
              for (String alias : certAliases) {
                  final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias, mUid);
                  if (certBytes == null) {
                      continue;
                  }
  
                  nonCaEntries.add(alias);
  
                  if (Arrays.equals(certBytes, targetCertBytes)) {
                      return alias;
                  }
              }
          }
  
          /*
           * Look at all the TrustedCertificateEntry types. Skip all the
           * PrivateKeyEntry we looked at above.
           */
          final String[] caAliases = mKeyStore.list(Credentials.CA_CERTIFICATE, mUid);
          if (certAliases != null) {
              for (String alias : caAliases) {
                  if (nonCaEntries.contains(alias)) {
                      continue;
                  }
  
                  final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid);
                  if (certBytes == null) {
                      continue;
                  }
  
                  if (Arrays.equals(certBytes, targetCertBytes)) {
                      return alias;
                  }
              }
          }
  
          return null;
      }
  
      @Override
      public void engineStore(OutputStream stream, char[] password) throws IOException,
              NoSuchAlgorithmException, CertificateException {
          throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
      }
  
      @Override
      public void engineLoad(InputStream stream, char[] password) throws IOException,
              NoSuchAlgorithmException, CertificateException {
          if (stream != null) {
              throw new IllegalArgumentException("InputStream not supported");
          }
  
          if (password != null) {
              throw new IllegalArgumentException("password not supported");
          }
  
          // Unfortunate name collision.
          mKeyStore = KeyStore.getInstance();
          mUid = KeyStore.UID_SELF;
      }
  
      @Override
      public void engineLoad(LoadStoreParameter param) throws IOException,
              NoSuchAlgorithmException, CertificateException {
          int uid = KeyStore.UID_SELF;
          if (param != null) {
              if (param instanceof AndroidKeyStoreLoadStoreParameter) {
                  uid = ((AndroidKeyStoreLoadStoreParameter) param).getUid();
              } else {
                  throw new IllegalArgumentException(
                          "Unsupported param type: " + param.getClass());
              }
          }
          mKeyStore = KeyStore.getInstance();
          mUid = uid;
      }
  
      @Override
      public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
              throws KeyStoreException {
          if (entry == null) {
              throw new KeyStoreException("entry == null");
          }
  
          Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
  
          if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) {
              java.security.KeyStore.TrustedCertificateEntry trE =
                      (java.security.KeyStore.TrustedCertificateEntry) entry;
              engineSetCertificateEntry(alias, trE.getTrustedCertificate());
              return;
          }
  
          if (entry instanceof PrivateKeyEntry) {
              PrivateKeyEntry prE = (PrivateKeyEntry) entry;
              setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param);
          } else if (entry instanceof SecretKeyEntry) {
              SecretKeyEntry secE = (SecretKeyEntry) entry;
              setSecretKeyEntry(alias, secE.getSecretKey(), param);
          } else if (entry instanceof WrappedKeyEntry) {
              WrappedKeyEntry wke = (WrappedKeyEntry) entry;
              setWrappedKeyEntry(alias, wke, param);
          } else {
              throw new KeyStoreException(
                      "Entry must be a PrivateKeyEntry, SecretKeyEntry or TrustedCertificateEntry"
                      + "; was " + entry);
          }
      }
  
      /**
       * {@link X509Certificate} which returns {@link AndroidKeyStorePublicKey} from
       * {@link #getPublicKey()}. This is so that crypto operations on these public keys contain
       * can find out which keystore private key entry to use. This is needed so that Android Keystore
       * crypto operations using public keys can find out which key alias to use. These operations
       * require an alias.
       */
      static class KeyStoreX509Certificate extends DelegatingX509Certificate {
          private final String mPrivateKeyAlias;
          private final int mPrivateKeyUid;
          KeyStoreX509Certificate(String privateKeyAlias, int privateKeyUid,
                  X509Certificate delegate) {
              super(delegate);
              mPrivateKeyAlias = privateKeyAlias;
              mPrivateKeyUid = privateKeyUid;
          }
  
          @Override
          public PublicKey getPublicKey() {
              PublicKey original = super.getPublicKey();
              return AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
                      mPrivateKeyAlias, mPrivateKeyUid,
                      original.getAlgorithm(), original.getEncoded());
          }
      }
  }
  