summaryrefslogtreecommitdiff
path: root/android/security
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2018-01-03 13:39:41 -0500
committerJustin Klaassen <justinklaassen@google.com>2018-01-03 13:39:41 -0500
commit98fe7819c6d14f4f464a5cac047f9e82dee5da58 (patch)
treea6b8b93eb21e205b27590ab5e2a1fb9efe27f892 /android/security
parent4217cf85c20565a3446a662a7f07f26137b26b7f (diff)
downloadandroid-28-98fe7819c6d14f4f464a5cac047f9e82dee5da58.tar.gz
Import Android SDK Platform P [4524038]
/google/data/ro/projects/android/fetch_artifact \ --bid 4524038 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4524038.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ic193bf1cf0cae78d4f2bfb4fbddfe42025c5c3c2
Diffstat (limited to 'android/security')
-rw-r--r--android/security/AttestedKeyPair.java75
-rw-r--r--android/security/Credentials.java34
-rw-r--r--android/security/KeyStore.java24
-rw-r--r--android/security/keymaster/KeyAttestationPackageInfo.java10
-rw-r--r--android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java2
-rw-r--r--android/security/keystore/AndroidKeyStoreProvider.java90
-rw-r--r--android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java5
-rw-r--r--android/security/keystore/AndroidKeyStoreSpi.java55
-rw-r--r--android/security/keystore/AttestationUtils.java53
-rw-r--r--android/security/keystore/KeyAttestationException.java46
-rw-r--r--android/security/keystore/KeyGenParameterSpec.java38
-rw-r--r--android/security/keystore/KeyProperties.java34
-rw-r--r--android/security/keystore/ParcelableKeyGenParameterSpec.java185
-rw-r--r--android/security/recoverablekeystore/KeyDerivationParameters.java112
-rw-r--r--android/security/recoverablekeystore/KeyEntryRecoveryData.java90
-rw-r--r--android/security/recoverablekeystore/KeyStoreRecoveryData.java115
-rw-r--r--android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java180
-rw-r--r--android/security/recoverablekeystore/RecoverableKeyStoreLoader.java467
18 files changed, 1498 insertions, 117 deletions
diff --git a/android/security/AttestedKeyPair.java b/android/security/AttestedKeyPair.java
new file mode 100644
index 00000000..c6bff5c1
--- /dev/null
+++ b/android/security/AttestedKeyPair.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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;
+
+import java.security.KeyPair;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The {@code AttestedKeyPair} class contains a {@code KeyPair} instance of
+ * keys generated by Keystore and owned by KeyChain, as well as an attestation
+ * record for the key.
+ *
+ * <p>Such keys can be obtained by calling
+ * {@link android.app.admin.DevicePolicyManager#generateKeyPair}.
+ */
+
+public final class AttestedKeyPair {
+ private final KeyPair mKeyPair;
+ private final Certificate[] mAttestationRecord;
+
+ /**
+ * @hide Only created by the platform, no need to expose as public API.
+ */
+ public AttestedKeyPair(KeyPair keyPair, Certificate[] attestationRecord) {
+ mKeyPair = keyPair;
+ mAttestationRecord = attestationRecord;
+ }
+
+ /**
+ * Returns the generated key pair associated with the attestation record
+ * in this instance.
+ */
+ public KeyPair getKeyPair() {
+ return mKeyPair;
+ }
+
+ /**
+ * Returns the attestation record for the key pair in this instance.
+ *
+ * The attestation record is a chain of certificates. The leaf certificate links to the public
+ * key of this key pair and other properties of the key or the device. If the key is in secure
+ * hardware, and if the secure hardware supports attestation, the leaf certificate will be
+ * signed by a chain of certificates rooted at a trustworthy CA key. Otherwise the chain will be
+ * rooted at an untrusted certificate.
+ *
+ * The attestation record could be for properties of the key, or include device identifiers.
+ *
+ * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestationChallenge}
+ * and <a href="https://developer.android.com/training/articles/security-key-attestation.html">
+ * Key Attestation</a> for the format of the attestation record inside the certificate.
+ */
+ public List<Certificate> getAttestationRecord() {
+ if (mAttestationRecord == null) {
+ return new ArrayList();
+ }
+ return Arrays.asList(mAttestationRecord);
+ }
+}
diff --git a/android/security/Credentials.java b/android/security/Credentials.java
index 6830a748..57db20be 100644
--- a/android/security/Credentials.java
+++ b/android/security/Credentials.java
@@ -60,10 +60,12 @@ public class Credentials {
/** Key prefix for user certificates. */
public static final String USER_CERTIFICATE = "USRCERT_";
- /** Key prefix for user private keys. */
+ /** Key prefix for user private and secret keys. */
public static final String USER_PRIVATE_KEY = "USRPKEY_";
- /** Key prefix for user secret keys. */
+ /** Key prefix for user secret keys.
+ * @deprecated use {@code USER_PRIVATE_KEY} for this category instead.
+ */
public static final String USER_SECRET_KEY = "USRSKEY_";
/** Key prefix for VPN. */
@@ -235,8 +237,7 @@ public class Credentials {
* Make sure every type is deleted. There can be all three types, so
* don't use a conditional here.
*/
- return deletePrivateKeyTypeForAlias(keystore, alias, uid)
- & deleteSecretKeyTypeForAlias(keystore, alias, uid)
+ return deleteUserKeyTypeForAlias(keystore, alias, uid)
& deleteCertificateTypesForAlias(keystore, alias, uid);
}
@@ -264,34 +265,27 @@ public class Credentials {
}
/**
- * Delete private key for a particular {@code alias}.
- * Returns {@code true} if the entry no longer exists.
- */
- static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias) {
- return deletePrivateKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF);
- }
-
- /**
- * Delete private key for a particular {@code alias}.
+ * Delete user key for a particular {@code alias}.
* Returns {@code true} if the entry no longer exists.
*/
- static boolean deletePrivateKeyTypeForAlias(KeyStore keystore, String alias, int uid) {
- return keystore.delete(Credentials.USER_PRIVATE_KEY + alias, uid);
+ public static boolean deleteUserKeyTypeForAlias(KeyStore keystore, String alias) {
+ return deleteUserKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF);
}
/**
- * Delete secret key for a particular {@code alias}.
+ * Delete user key for a particular {@code alias}.
* Returns {@code true} if the entry no longer exists.
*/
- public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias) {
- return deleteSecretKeyTypeForAlias(keystore, alias, KeyStore.UID_SELF);
+ public static boolean deleteUserKeyTypeForAlias(KeyStore keystore, String alias, int uid) {
+ return keystore.delete(Credentials.USER_PRIVATE_KEY + alias, uid) ||
+ keystore.delete(Credentials.USER_SECRET_KEY + alias, uid);
}
/**
- * Delete secret key for a particular {@code alias}.
+ * Delete legacy prefixed entry for a particular {@code alias}
* Returns {@code true} if the entry no longer exists.
*/
- public static boolean deleteSecretKeyTypeForAlias(KeyStore keystore, String alias, int uid) {
+ public static boolean deleteLegacyKeyForAlias(KeyStore keystore, String alias, int uid) {
return keystore.delete(Credentials.USER_SECRET_KEY + alias, uid);
}
}
diff --git a/android/security/KeyStore.java b/android/security/KeyStore.java
index 399dddd7..fabcdf00 100644
--- a/android/security/KeyStore.java
+++ b/android/security/KeyStore.java
@@ -95,6 +95,16 @@ public class KeyStore {
public static final int FLAG_ENCRYPTED = 1;
/**
+ * Select Software keymaster device, which as of this writing is the lowest security
+ * level available on an android device. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
+ * A TEE based keymaster implementation is implied.
+ *
+ * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
+ * For historical reasons this corresponds to the KEYSTORE_FLAG_FALLBACK flag.
+ */
+ public static final int FLAG_SOFTWARE = 1 << 1;
+
+ /**
* A private flag that's only available to system server to indicate that this key is part of
* device encryption flow so it receives special treatment from keystore. For example this key
* will not be super encrypted, and it will be stored separately under an unique UID instead
@@ -104,6 +114,16 @@ public class KeyStore {
*/
public static final int FLAG_CRITICAL_TO_DEVICE_ENCRYPTION = 1 << 3;
+ /**
+ * Select Strongbox keymaster device, which as of this writing the the highest security level
+ * available an android devices. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
+ * A TEE based keymaster implementation is implied.
+ *
+ * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
+ */
+ public static final int FLAG_STRONGBOX = 1 << 4;
+
+
// States
public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
@@ -440,9 +460,9 @@ public class KeyStore {
return mError;
}
- public boolean addRngEntropy(byte[] data) {
+ public boolean addRngEntropy(byte[] data, int flags) {
try {
- return mBinder.addRngEntropy(data) == NO_ERROR;
+ return mBinder.addRngEntropy(data, flags) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
diff --git a/android/security/keymaster/KeyAttestationPackageInfo.java b/android/security/keymaster/KeyAttestationPackageInfo.java
index 5a3f3907..a93d1e11 100644
--- a/android/security/keymaster/KeyAttestationPackageInfo.java
+++ b/android/security/keymaster/KeyAttestationPackageInfo.java
@@ -28,7 +28,7 @@ import android.os.Parcelable;
*/
public class KeyAttestationPackageInfo implements Parcelable {
private final String mPackageName;
- private final int mPackageVersionCode;
+ private final long mPackageVersionCode;
private final Signature[] mPackageSignatures;
/**
@@ -37,7 +37,7 @@ public class KeyAttestationPackageInfo implements Parcelable {
* @param mPackageSignatures
*/
public KeyAttestationPackageInfo(
- String mPackageName, int mPackageVersionCode, Signature[] mPackageSignatures) {
+ String mPackageName, long mPackageVersionCode, Signature[] mPackageSignatures) {
super();
this.mPackageName = mPackageName;
this.mPackageVersionCode = mPackageVersionCode;
@@ -52,7 +52,7 @@ public class KeyAttestationPackageInfo implements Parcelable {
/**
* @return the mPackageVersionCode
*/
- public int getPackageVersionCode() {
+ public long getPackageVersionCode() {
return mPackageVersionCode;
}
/**
@@ -70,7 +70,7 @@ public class KeyAttestationPackageInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mPackageName);
- dest.writeInt(mPackageVersionCode);
+ dest.writeLong(mPackageVersionCode);
dest.writeTypedArray(mPackageSignatures, flags);
}
@@ -89,7 +89,7 @@ public class KeyAttestationPackageInfo implements Parcelable {
private KeyAttestationPackageInfo(Parcel source) {
mPackageName = source.readString();
- mPackageVersionCode = source.readInt();
+ mPackageVersionCode = source.readLong();
mPackageSignatures = source.createTypedArray(Signature.CREATOR);
}
}
diff --git a/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 988e32cf..f1d1e166 100644
--- a/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -305,7 +305,7 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
int flags = 0;
- String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias();
+ String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
boolean success = false;
try {
diff --git a/android/security/keystore/AndroidKeyStoreProvider.java b/android/security/keystore/AndroidKeyStoreProvider.java
index f36c00ce..55e6519d 100644
--- a/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/android/security/keystore/AndroidKeyStoreProvider.java
@@ -196,7 +196,7 @@ public class AndroidKeyStoreProvider extends Provider {
}
@NonNull
- public static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
+ private static AndroidKeyStorePrivateKey getAndroidKeyStorePrivateKey(
@NonNull AndroidKeyStorePublicKey publicKey) {
String keyAlgorithm = publicKey.getAlgorithm();
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
@@ -212,17 +212,25 @@ public class AndroidKeyStoreProvider extends Provider {
}
@NonNull
- public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
- @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
+ private static KeyCharacteristics getKeyCharacteristics(@NonNull KeyStore keyStore,
+ @NonNull String alias, int uid)
throws UnrecoverableKeyException {
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int errorCode = keyStore.getKeyCharacteristics(
- privateKeyAlias, null, null, uid, keyCharacteristics);
+ alias, null, null, uid, keyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
throw (UnrecoverableKeyException)
- new UnrecoverableKeyException("Failed to obtain information about private key")
- .initCause(KeyStore.getKeyStoreException(errorCode));
+ new UnrecoverableKeyException("Failed to obtain information about key")
+ .initCause(KeyStore.getKeyStoreException(errorCode));
}
+ return keyCharacteristics;
+ }
+
+ @NonNull
+ private static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
+ KeyCharacteristics keyCharacteristics)
+ throws UnrecoverableKeyException {
ExportResult exportResult = keyStore.exportKey(
privateKeyAlias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null, uid);
if (exportResult.resultCode != KeyStore.NO_ERROR) {
@@ -252,37 +260,56 @@ public class AndroidKeyStoreProvider extends Provider {
}
@NonNull
- public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
+ public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
throws UnrecoverableKeyException {
+ return loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid,
+ getKeyCharacteristics(keyStore, privateKeyAlias, uid));
+ }
+
+ @NonNull
+ private static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
+ @NonNull KeyCharacteristics keyCharacteristics)
+ throws UnrecoverableKeyException {
AndroidKeyStorePublicKey publicKey =
- loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid);
+ loadAndroidKeyStorePublicKeyFromKeystore(keyStore, privateKeyAlias, uid,
+ keyCharacteristics);
AndroidKeyStorePrivateKey privateKey =
AndroidKeyStoreProvider.getAndroidKeyStorePrivateKey(publicKey);
return new KeyPair(publicKey, privateKey);
}
@NonNull
- public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+ public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
@NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
throws UnrecoverableKeyException {
- KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid);
+ return loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid,
+ getKeyCharacteristics(keyStore, privateKeyAlias, uid));
+ }
+
+ @NonNull
+ private static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid,
+ @NonNull KeyCharacteristics keyCharacteristics)
+ throws UnrecoverableKeyException {
+ KeyPair keyPair = loadAndroidKeyStoreKeyPairFromKeystore(keyStore, privateKeyAlias, uid,
+ keyCharacteristics);
return (AndroidKeyStorePrivateKey) keyPair.getPrivate();
}
@NonNull
- public static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
- @NonNull KeyStore keyStore, @NonNull String secretKeyAlias, int uid)
+ public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String privateKeyAlias, int uid)
throws UnrecoverableKeyException {
- KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
- int errorCode = keyStore.getKeyCharacteristics(
- secretKeyAlias, null, null, uid, keyCharacteristics);
- if (errorCode != KeyStore.NO_ERROR) {
- throw (UnrecoverableKeyException)
- new UnrecoverableKeyException("Failed to obtain information about key")
- .initCause(KeyStore.getKeyStoreException(errorCode));
- }
+ return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, privateKeyAlias, uid,
+ getKeyCharacteristics(keyStore, privateKeyAlias, uid));
+ }
+ @NonNull
+ private static AndroidKeyStoreSecretKey loadAndroidKeyStoreSecretKeyFromKeystore(
+ @NonNull String secretKeyAlias, int uid, @NonNull KeyCharacteristics keyCharacteristics)
+ throws UnrecoverableKeyException {
Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
if (keymasterAlgorithm == null) {
throw new UnrecoverableKeyException("Key algorithm unknown");
@@ -310,6 +337,29 @@ public class AndroidKeyStoreProvider extends Provider {
return new AndroidKeyStoreSecretKey(secretKeyAlias, uid, keyAlgorithmString);
}
+ public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
+ @NonNull KeyStore keyStore, @NonNull String userKeyAlias, int uid)
+ throws UnrecoverableKeyException {
+ KeyCharacteristics keyCharacteristics = getKeyCharacteristics(keyStore, userKeyAlias, uid);
+
+ Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM);
+ if (keymasterAlgorithm == null) {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+
+ if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC ||
+ keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) {
+ return loadAndroidKeyStoreSecretKeyFromKeystore(userKeyAlias, uid,
+ keyCharacteristics);
+ } else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA ||
+ keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
+ return loadAndroidKeyStorePrivateKeyFromKeystore(keyStore, userKeyAlias, uid,
+ keyCharacteristics);
+ } else {
+ throw new UnrecoverableKeyException("Key algorithm unknown");
+ }
+ }
+
/**
* Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID.
* The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID
diff --git a/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 0379863e..fdb885db 100644
--- a/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -64,7 +64,10 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key;
String keyAliasInKeystore = keystoreKey.getAlias();
String entryAlias;
- if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
+ if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) {
+ entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length());
+ } else if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)){
+ // key has legacy prefix
entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
} else {
throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
diff --git a/android/security/keystore/AndroidKeyStoreSpi.java b/android/security/keystore/AndroidKeyStoreSpi.java
index bab4010b..d73a9e29 100644
--- a/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/android/security/keystore/AndroidKeyStoreSpi.java
@@ -89,18 +89,14 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
UnrecoverableKeyException {
- if (isPrivateKeyEntry(alias)) {
- String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
- return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
- mKeyStore, privateKeyAlias, mUid);
- } else if (isSecretKeyEntry(alias)) {
- String secretKeyAlias = Credentials.USER_SECRET_KEY + alias;
- return AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(
- mKeyStore, secretKeyAlias, mUid);
- } else {
- // Key not found
- return null;
+ String userKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
+ if (!mKeyStore.contains(userKeyAlias, mUid)) {
+ // try legacy prefix for backward compatibility
+ userKeyAlias = Credentials.USER_SECRET_KEY + alias;
+ if (!mKeyStore.contains(userKeyAlias, mUid)) return null;
}
+ return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, userKeyAlias,
+ mUid);
}
@Override
@@ -540,7 +536,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
} else {
// Keep the stored private key around -- delete all other entry types
Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
- Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias, mUid);
+ Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
}
// Store the leaf certificate
@@ -565,7 +561,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
Credentials.deleteAllTypesForAlias(mKeyStore, alias, mUid);
} else {
Credentials.deleteCertificateTypesForAlias(mKeyStore, alias, mUid);
- Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias, mUid);
+ Credentials.deleteLegacyKeyForAlias(mKeyStore, alias, mUid);
}
}
}
@@ -588,12 +584,17 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
if (keyAliasInKeystore == null) {
throw new KeyStoreException("KeyStore-backed secret key does not have an alias");
}
- if (!keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
- throw new KeyStoreException("KeyStore-backed secret key has invalid alias: "
- + keyAliasInKeystore);
+ 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(Credentials.USER_SECRET_KEY.length());
+ keyAliasInKeystore.substring(keyAliasPrefix.length());
if (!entryAlias.equals(keyEntryAlias)) {
throw new KeyStoreException("Can only replace KeyStore-backed keys with same"
+ " alias: " + entryAlias + " != " + keyEntryAlias);
@@ -728,7 +729,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias, mUid);
- String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
+ String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + entryAlias;
int errorCode = mKeyStore.importKey(
keyAliasInKeystore,
args,
@@ -827,24 +828,10 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
private boolean isKeyEntry(String alias) {
- return isPrivateKeyEntry(alias) || isSecretKeyEntry(alias);
- }
-
- private boolean isPrivateKeyEntry(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
-
- return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid);
+ return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias, mUid) ||
+ mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid);
}
- private boolean isSecretKeyEntry(String alias) {
- if (alias == null) {
- throw new NullPointerException("alias == null");
- }
-
- return mKeyStore.contains(Credentials.USER_SECRET_KEY + alias, mUid);
- }
private boolean isCertificateEntry(String alias) {
if (alias == null) {
diff --git a/android/security/keystore/AttestationUtils.java b/android/security/keystore/AttestationUtils.java
index cf4347d1..0811100f 100644
--- a/android/security/keystore/AttestationUtils.java
+++ b/android/security/keystore/AttestationUtils.java
@@ -73,6 +73,33 @@ public abstract class AttestationUtils {
public static final int ID_TYPE_MEID = 3;
/**
+ * Creates an array of X509Certificates from the provided KeymasterCertificateChain.
+ *
+ * @hide Only called by the DevicePolicyManager.
+ */
+ @NonNull public static X509Certificate[] parseCertificateChain(
+ final KeymasterCertificateChain kmChain) throws
+ KeyAttestationException {
+ // Extract certificate chain.
+ final Collection<byte[]> rawChain = kmChain.getCertificates();
+ if (rawChain.size() < 2) {
+ throw new KeyAttestationException("Attestation certificate chain contained "
+ + rawChain.size() + " entries. At least two are required.");
+ }
+ final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream();
+ try {
+ for (final byte[] cert : rawChain) {
+ concatenatedRawChain.write(cert);
+ }
+ return CertificateFactory.getInstance("X.509").generateCertificates(
+ new ByteArrayInputStream(concatenatedRawChain.toByteArray()))
+ .toArray(new X509Certificate[0]);
+ } catch (Exception e) {
+ throw new KeyAttestationException("Unable to construct certificate chain", e);
+ }
+ }
+
+ /**
* Performs attestation of the device's identifiers. This method returns a certificate chain
* whose first element contains the requested device identifiers in an extension. The device's
* manufacturer, model, brand, device and product are always also included in the attestation.
@@ -173,22 +200,18 @@ public abstract class AttestationUtils {
KeyStore.getKeyStoreException(errorCode));
}
- // Extract certificate chain.
- final Collection<byte[]> rawChain = outChain.getCertificates();
- if (rawChain.size() < 2) {
- throw new DeviceIdAttestationException("Attestation certificate chain contained "
- + rawChain.size() + " entries. At least two are required.");
- }
- final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream();
try {
- for (final byte[] cert : rawChain) {
- concatenatedRawChain.write(cert);
- }
- return CertificateFactory.getInstance("X.509").generateCertificates(
- new ByteArrayInputStream(concatenatedRawChain.toByteArray()))
- .toArray(new X509Certificate[0]);
- } catch (Exception e) {
- throw new DeviceIdAttestationException("Unable to construct certificate chain", e);
+ return parseCertificateChain(outChain);
+ } catch (KeyAttestationException e) {
+ throw new DeviceIdAttestationException(e.getMessage(), e);
}
}
+
+ /**
+ * Returns true if the attestation chain provided is a valid key attestation chain.
+ * @hide
+ */
+ public static boolean isChainValid(KeymasterCertificateChain chain) {
+ return chain != null && chain.getCertificates().size() >= 2;
+ }
}
diff --git a/android/security/keystore/KeyAttestationException.java b/android/security/keystore/KeyAttestationException.java
new file mode 100644
index 00000000..6cf5fb2f
--- /dev/null
+++ b/android/security/keystore/KeyAttestationException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * Thrown when {@link AttestationUtils} is unable to attest the given key or handle
+ * the resulting attestation record.
+ *
+ * @hide
+ */
+public class KeyAttestationException extends Exception {
+ /**
+ * Constructs a new {@code KeyAttestationException} with the current stack trace and the
+ * specified detail message.
+ *
+ * @param detailMessage the detail message for this exception.
+ */
+ public KeyAttestationException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ /**
+ * Constructs a new {@code KeyAttestationException} with the current stack trace, the
+ * specified detail message and the specified cause.
+ *
+ * @param message the detail message for this exception.
+ * @param cause the cause of this exception, may be {@code null}.
+ */
+ public KeyAttestationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/android/security/keystore/KeyGenParameterSpec.java b/android/security/keystore/KeyGenParameterSpec.java
index ed40b77b..1238d877 100644
--- a/android/security/keystore/KeyGenParameterSpec.java
+++ b/android/security/keystore/KeyGenParameterSpec.java
@@ -195,7 +195,7 @@ import javax.security.auth.x500.X500Principal;
* <pre> {@code
* KeyGenerator keyGenerator = KeyGenerator.getInstance(
* KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
- * keyGenerator.initialize(
+ * keyGenerator.init(
* new KeyGenParameterSpec.Builder("key2",
* KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
* .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -219,7 +219,7 @@ import javax.security.auth.x500.X500Principal;
* <pre> {@code
* KeyGenerator keyGenerator = KeyGenerator.getInstance(
* KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
- * keyGenerator.initialize(
+ * keyGenerator.init(
* new KeyGenParameterSpec.Builder("key2", KeyProperties.PURPOSE_SIGN).build());
* SecretKey key = keyGenerator.generateKey();
* Mac mac = Mac.getInstance("HmacSHA256");
@@ -680,6 +680,40 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
+ * A Builder constructor taking in an already-built KeyGenParameterSpec, useful for
+ * changing values of the KeyGenParameterSpec quickly.
+ * @hide Should be used internally only.
+ */
+ public Builder(@NonNull KeyGenParameterSpec sourceSpec) {
+ this(sourceSpec.getKeystoreAlias(), sourceSpec.getPurposes());
+ mUid = sourceSpec.getUid();
+ mKeySize = sourceSpec.getKeySize();
+ mSpec = sourceSpec.getAlgorithmParameterSpec();
+ mCertificateSubject = sourceSpec.getCertificateSubject();
+ mCertificateSerialNumber = sourceSpec.getCertificateSerialNumber();
+ mCertificateNotBefore = sourceSpec.getCertificateNotBefore();
+ mCertificateNotAfter = sourceSpec.getCertificateNotAfter();
+ mKeyValidityStart = sourceSpec.getKeyValidityStart();
+ mKeyValidityForOriginationEnd = sourceSpec.getKeyValidityForOriginationEnd();
+ mKeyValidityForConsumptionEnd = sourceSpec.getKeyValidityForConsumptionEnd();
+ mPurposes = sourceSpec.getPurposes();
+ if (sourceSpec.isDigestsSpecified()) {
+ mDigests = sourceSpec.getDigests();
+ }
+ mEncryptionPaddings = sourceSpec.getEncryptionPaddings();
+ mSignaturePaddings = sourceSpec.getSignaturePaddings();
+ mBlockModes = sourceSpec.getBlockModes();
+ mRandomizedEncryptionRequired = sourceSpec.isRandomizedEncryptionRequired();
+ mUserAuthenticationRequired = sourceSpec.isUserAuthenticationRequired();
+ mUserAuthenticationValidityDurationSeconds =
+ sourceSpec.getUserAuthenticationValidityDurationSeconds();
+ mAttestationChallenge = sourceSpec.getAttestationChallenge();
+ mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
+ mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
+ mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment();
+ }
+
+ /**
* Sets the UID which will own the key.
*
* @param uid UID or {@code -1} for the UID of the current process.
diff --git a/android/security/keystore/KeyProperties.java b/android/security/keystore/KeyProperties.java
index d6b1cf1d..a250d1f0 100644
--- a/android/security/keystore/KeyProperties.java
+++ b/android/security/keystore/KeyProperties.java
@@ -39,13 +39,12 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true,
- value = {
- PURPOSE_ENCRYPT,
- PURPOSE_DECRYPT,
- PURPOSE_SIGN,
- PURPOSE_VERIFY,
- })
+ @IntDef(flag = true, prefix = { "PURPOSE_" }, value = {
+ PURPOSE_ENCRYPT,
+ PURPOSE_DECRYPT,
+ PURPOSE_SIGN,
+ PURPOSE_VERIFY,
+ })
public @interface PurposeEnum {}
/**
@@ -126,7 +125,7 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @StringDef({
+ @StringDef(prefix = { "KEY_" }, value = {
KEY_ALGORITHM_RSA,
KEY_ALGORITHM_EC,
KEY_ALGORITHM_AES,
@@ -267,7 +266,7 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @StringDef({
+ @StringDef(prefix = { "BLOCK_MODE_" }, value = {
BLOCK_MODE_ECB,
BLOCK_MODE_CBC,
BLOCK_MODE_CTR,
@@ -354,7 +353,7 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @StringDef({
+ @StringDef(prefix = { "ENCRYPTION_PADDING_" }, value = {
ENCRYPTION_PADDING_NONE,
ENCRYPTION_PADDING_PKCS7,
ENCRYPTION_PADDING_RSA_PKCS1,
@@ -437,7 +436,7 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @StringDef({
+ @StringDef(prefix = { "SIGNATURE_PADDING_" }, value = {
SIGNATURE_PADDING_RSA_PKCS1,
SIGNATURE_PADDING_RSA_PSS,
})
@@ -497,7 +496,7 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @StringDef({
+ @StringDef(prefix = { "DIGEST_" }, value = {
DIGEST_NONE,
DIGEST_MD5,
DIGEST_SHA1,
@@ -647,11 +646,12 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({
- ORIGIN_GENERATED,
- ORIGIN_IMPORTED,
- ORIGIN_UNKNOWN,
- })
+ @IntDef(prefix = { "ORIGIN_" }, value = {
+ ORIGIN_GENERATED,
+ ORIGIN_IMPORTED,
+ ORIGIN_UNKNOWN,
+ })
+
public @interface OriginEnum {}
/** Key was generated inside AndroidKeyStore. */
diff --git a/android/security/keystore/ParcelableKeyGenParameterSpec.java b/android/security/keystore/ParcelableKeyGenParameterSpec.java
new file mode 100644
index 00000000..7cb8e375
--- /dev/null
+++ b/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 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.os.Parcelable;
+import android.os.Parcel;
+
+import java.math.BigInteger;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A parcelable version of KeyGenParameterSpec
+ * @hide only used for communicating with the DPMS.
+ */
+public final class ParcelableKeyGenParameterSpec implements Parcelable {
+ private static final int ALGORITHM_PARAMETER_SPEC_NONE = 1;
+ private static final int ALGORITHM_PARAMETER_SPEC_RSA = 2;
+ private static final int ALGORITHM_PARAMETER_SPEC_EC = 3;
+
+ private final KeyGenParameterSpec mSpec;
+
+ public ParcelableKeyGenParameterSpec(
+ KeyGenParameterSpec spec) {
+ mSpec = spec;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ private static void writeOptionalDate(Parcel out, Date date) {
+ if (date != null) {
+ out.writeBoolean(true);
+ out.writeLong(date.getTime());
+ } else {
+ out.writeBoolean(false);
+ }
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mSpec.getKeystoreAlias());
+ out.writeInt(mSpec.getPurposes());
+ out.writeInt(mSpec.getUid());
+ out.writeInt(mSpec.getKeySize());
+
+ // Only needs to support RSAKeyGenParameterSpec and ECGenParameterSpec.
+ AlgorithmParameterSpec algoSpec = mSpec.getAlgorithmParameterSpec();
+ if (algoSpec == null) {
+ out.writeInt(ALGORITHM_PARAMETER_SPEC_NONE);
+ } else if (algoSpec instanceof RSAKeyGenParameterSpec) {
+ RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algoSpec;
+ out.writeInt(ALGORITHM_PARAMETER_SPEC_RSA);
+ out.writeInt(rsaSpec.getKeysize());
+ out.writeByteArray(rsaSpec.getPublicExponent().toByteArray());
+ } else if (algoSpec instanceof ECGenParameterSpec) {
+ ECGenParameterSpec ecSpec = (ECGenParameterSpec) algoSpec;
+ out.writeInt(ALGORITHM_PARAMETER_SPEC_EC);
+ out.writeString(ecSpec.getName());
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Unknown algorithm parameter spec: %s", algoSpec.getClass()));
+ }
+ out.writeByteArray(mSpec.getCertificateSubject().getEncoded());
+ out.writeByteArray(mSpec.getCertificateSerialNumber().toByteArray());
+ out.writeLong(mSpec.getCertificateNotBefore().getTime());
+ out.writeLong(mSpec.getCertificateNotAfter().getTime());
+ writeOptionalDate(out, mSpec.getKeyValidityStart());
+ writeOptionalDate(out, mSpec.getKeyValidityForOriginationEnd());
+ writeOptionalDate(out, mSpec.getKeyValidityForConsumptionEnd());
+ if (mSpec.isDigestsSpecified()) {
+ out.writeStringArray(mSpec.getDigests());
+ } else {
+ out.writeStringArray(null);
+ }
+ out.writeStringArray(mSpec.getEncryptionPaddings());
+ out.writeStringArray(mSpec.getSignaturePaddings());
+ out.writeStringArray(mSpec.getBlockModes());
+ out.writeBoolean(mSpec.isRandomizedEncryptionRequired());
+ out.writeBoolean(mSpec.isUserAuthenticationRequired());
+ out.writeInt(mSpec.getUserAuthenticationValidityDurationSeconds());
+ out.writeByteArray(mSpec.getAttestationChallenge());
+ out.writeBoolean(mSpec.isUniqueIdIncluded());
+ out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
+ out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
+ }
+
+ private static Date readDateOrNull(Parcel in) {
+ boolean hasDate = in.readBoolean();
+ if (hasDate) {
+ return new Date(in.readLong());
+ } else {
+ return null;
+ }
+ }
+
+ private ParcelableKeyGenParameterSpec(Parcel in) {
+ String keystoreAlias = in.readString();
+ int purposes = in.readInt();
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
+ keystoreAlias, purposes);
+ builder.setUid(in.readInt());
+ // KeySize is -1 by default, if the KeyGenParameterSpec previously parcelled had the default
+ // value, do not set it as this will cause setKeySize to throw.
+ int keySize = in.readInt();
+ if (keySize >= 0) {
+ builder.setKeySize(keySize);
+ }
+
+ int keySpecType = in.readInt();
+ AlgorithmParameterSpec algorithmSpec = null;
+ if (keySpecType == ALGORITHM_PARAMETER_SPEC_NONE) {
+ algorithmSpec = null;
+ } else if (keySpecType == ALGORITHM_PARAMETER_SPEC_RSA) {
+ int rsaKeySize = in.readInt();
+ BigInteger publicExponent = new BigInteger(in.createByteArray());
+ algorithmSpec = new RSAKeyGenParameterSpec(rsaKeySize, publicExponent);
+ } else if (keySpecType == ALGORITHM_PARAMETER_SPEC_EC) {
+ String stdName = in.readString();
+ algorithmSpec = new ECGenParameterSpec(stdName);
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Unknown algorithm parameter spec: %d", keySpecType));
+ }
+ if (algorithmSpec != null) {
+ builder.setAlgorithmParameterSpec(algorithmSpec);
+ }
+ builder.setCertificateSubject(new X500Principal(in.createByteArray()));
+ builder.setCertificateSerialNumber(new BigInteger(in.createByteArray()));
+ builder.setCertificateNotBefore(new Date(in.readLong()));
+ builder.setCertificateNotAfter(new Date(in.readLong()));
+ builder.setKeyValidityStart(readDateOrNull(in));
+ builder.setKeyValidityForOriginationEnd(readDateOrNull(in));
+ builder.setKeyValidityForConsumptionEnd(readDateOrNull(in));
+ String[] digests = in.createStringArray();
+ if (digests != null) {
+ builder.setDigests(digests);
+ }
+ builder.setEncryptionPaddings(in.createStringArray());
+ builder.setSignaturePaddings(in.createStringArray());
+ builder.setBlockModes(in.createStringArray());
+ builder.setRandomizedEncryptionRequired(in.readBoolean());
+ builder.setUserAuthenticationRequired(in.readBoolean());
+ builder.setUserAuthenticationValidityDurationSeconds(in.readInt());
+ builder.setAttestationChallenge(in.createByteArray());
+ builder.setUniqueIdIncluded(in.readBoolean());
+ builder.setUserAuthenticationValidWhileOnBody(in.readBoolean());
+ builder.setInvalidatedByBiometricEnrollment(in.readBoolean());
+ mSpec = builder.build();
+ }
+
+ public static final Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
+ @Override
+ public ParcelableKeyGenParameterSpec createFromParcel(Parcel in) {
+ return new ParcelableKeyGenParameterSpec(in);
+ }
+
+ @Override
+ public ParcelableKeyGenParameterSpec[] newArray(int size) {
+ return new ParcelableKeyGenParameterSpec[size];
+ }
+ };
+
+ public KeyGenParameterSpec getSpec() {
+ return mSpec;
+ }
+}
diff --git a/android/security/recoverablekeystore/KeyDerivationParameters.java b/android/security/recoverablekeystore/KeyDerivationParameters.java
new file mode 100644
index 00000000..978e60ee
--- /dev/null
+++ b/android/security/recoverablekeystore/KeyDerivationParameters.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.recoverablekeystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Collection of parameters which define a key derivation function.
+ * Supports
+ *
+ * <ul>
+ * <li>SHA256
+ * <li>Argon2id
+ * </ul>
+ * @hide
+ */
+public final class KeyDerivationParameters implements Parcelable {
+ private final int mAlgorithm;
+ private byte[] mSalt;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALGORITHM_SHA256, ALGORITHM_ARGON2ID})
+ public @interface KeyDerivationAlgorithm {
+ }
+
+ /**
+ * Salted SHA256
+ */
+ public static final int ALGORITHM_SHA256 = 1;
+
+ /**
+ * Argon2ID
+ */
+ // TODO: add Argon2ID support.
+ public static final int ALGORITHM_ARGON2ID = 2;
+
+ /**
+ * Creates instance of the class to to derive key using salted SHA256 hash.
+ */
+ public static KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
+ return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
+ }
+
+ private KeyDerivationParameters(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
+ mAlgorithm = algorithm;
+ mSalt = Preconditions.checkNotNull(salt);
+ }
+
+ /**
+ * Gets algorithm.
+ */
+ public @KeyDerivationAlgorithm int getAlgorithm() {
+ return mAlgorithm;
+ }
+
+ /**
+ * Gets salt.
+ */
+ public @NonNull byte[] getSalt() {
+ return mSalt;
+ }
+
+ public static final Parcelable.Creator<KeyDerivationParameters> CREATOR =
+ new Parcelable.Creator<KeyDerivationParameters>() {
+ public KeyDerivationParameters createFromParcel(Parcel in) {
+ return new KeyDerivationParameters(in);
+ }
+
+ public KeyDerivationParameters[] newArray(int length) {
+ return new KeyDerivationParameters[length];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mAlgorithm);
+ out.writeByteArray(mSalt);
+ }
+
+ protected KeyDerivationParameters(Parcel in) {
+ mAlgorithm = in.readInt();
+ mSalt = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/security/recoverablekeystore/KeyEntryRecoveryData.java b/android/security/recoverablekeystore/KeyEntryRecoveryData.java
new file mode 100644
index 00000000..80f5aa71
--- /dev/null
+++ b/android/security/recoverablekeystore/KeyEntryRecoveryData.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.recoverablekeystore;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+
+/**
+ * Helper class with data necessary recover a single application key, given a recovery key.
+ *
+ * <ul>
+ * <li>Alias - Keystore alias of the key.
+ * <li>Encrypted key material.
+ * </ul>
+ *
+ * Note that Application info is not included. Recovery Agent can only make its own keys
+ * recoverable.
+ *
+ * @hide
+ */
+public final class KeyEntryRecoveryData implements Parcelable {
+ private final byte[] mAlias;
+ // The only supported format is AES-256 symmetric key.
+ private final byte[] mEncryptedKeyMaterial;
+
+ public KeyEntryRecoveryData(@NonNull byte[] alias, @NonNull byte[] encryptedKeyMaterial) {
+ mAlias = Preconditions.checkNotNull(alias);
+ mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
+ }
+
+ /**
+ * Application-specific alias of the key.
+ * @see java.security.KeyStore.aliases
+ */
+ public @NonNull byte[] getAlias() {
+ return mAlias;
+ }
+
+ /**
+ * Encrypted key material encrypted by recovery key.
+ */
+ public @NonNull byte[] getEncryptedKeyMaterial() {
+ return mEncryptedKeyMaterial;
+ }
+
+ public static final Parcelable.Creator<KeyEntryRecoveryData> CREATOR =
+ new Parcelable.Creator<KeyEntryRecoveryData>() {
+ public KeyEntryRecoveryData createFromParcel(Parcel in) {
+ return new KeyEntryRecoveryData(in);
+ }
+
+ public KeyEntryRecoveryData[] newArray(int length) {
+ return new KeyEntryRecoveryData[length];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeByteArray(mAlias);
+ out.writeByteArray(mEncryptedKeyMaterial);
+ }
+
+ protected KeyEntryRecoveryData(Parcel in) {
+ mAlias = in.createByteArray();
+ mEncryptedKeyMaterial = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/security/recoverablekeystore/KeyStoreRecoveryData.java b/android/security/recoverablekeystore/KeyStoreRecoveryData.java
new file mode 100644
index 00000000..087f7a25
--- /dev/null
+++ b/android/security/recoverablekeystore/KeyStoreRecoveryData.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 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.recoverablekeystore;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Helper class which returns data necessary to recover keys.
+ * Contains
+ *
+ * <ul>
+ * <li>Snapshot version.
+ * <li>Recovery metadata with UI and key derivation parameters.
+ * <li>List of application keys encrypted by recovery key.
+ * <li>Encrypted recovery key.
+ * </ul>
+ *
+ * @hide
+ */
+public final class KeyStoreRecoveryData implements Parcelable {
+ private final int mSnapshotVersion;
+ private final List<KeyStoreRecoveryMetadata> mRecoveryMetadata;
+ private final List<KeyEntryRecoveryData> mApplicationKeyBlobs;
+ private final byte[] mEncryptedRecoveryKeyBlob;
+
+ public KeyStoreRecoveryData(int snapshotVersion, @NonNull List<KeyStoreRecoveryMetadata>
+ recoveryMetadata, @NonNull List<KeyEntryRecoveryData> applicationKeyBlobs,
+ @NonNull byte[] encryptedRecoveryKeyBlob) {
+ mSnapshotVersion = snapshotVersion;
+ mRecoveryMetadata = Preconditions.checkNotNull(recoveryMetadata);
+ mApplicationKeyBlobs = Preconditions.checkNotNull(applicationKeyBlobs);
+ mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob);
+ }
+
+ /**
+ * Snapshot version for given account. It is incremented when user secret or list of application
+ * keys changes.
+ */
+ public int getSnapshotVersion() {
+ return mSnapshotVersion;
+ }
+
+ /**
+ * UI and key derivation parameters. Note that combination of secrets may be used.
+ */
+ public @NonNull List<KeyStoreRecoveryMetadata> getRecoveryMetadata() {
+ return mRecoveryMetadata;
+ }
+
+ /**
+ * List of application keys, with key material encrypted by
+ * the recovery key ({@link #getEncryptedRecoveryKeyBlob}).
+ */
+ public @NonNull List<KeyEntryRecoveryData> getApplicationKeyBlobs() {
+ return mApplicationKeyBlobs;
+ }
+
+ /**
+ * Recovery key blob, encrypted by user secret and recovery service public key.
+ */
+ public @NonNull byte[] getEncryptedRecoveryKeyBlob() {
+ return mEncryptedRecoveryKeyBlob;
+ }
+
+ public static final Parcelable.Creator<KeyStoreRecoveryData> CREATOR =
+ new Parcelable.Creator<KeyStoreRecoveryData>() {
+ public KeyStoreRecoveryData createFromParcel(Parcel in) {
+ return new KeyStoreRecoveryData(in);
+ }
+
+ public KeyStoreRecoveryData[] newArray(int length) {
+ return new KeyStoreRecoveryData[length];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mSnapshotVersion);
+ out.writeTypedList(mRecoveryMetadata);
+ out.writeByteArray(mEncryptedRecoveryKeyBlob);
+ out.writeTypedList(mApplicationKeyBlobs);
+ }
+
+ protected KeyStoreRecoveryData(Parcel in) {
+ mSnapshotVersion = in.readInt();
+ mRecoveryMetadata = in.createTypedArrayList(KeyStoreRecoveryMetadata.CREATOR);
+ mEncryptedRecoveryKeyBlob = in.createByteArray();
+ mApplicationKeyBlobs = in.createTypedArrayList(KeyEntryRecoveryData.CREATOR);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java b/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java
new file mode 100644
index 00000000..43f9c805
--- /dev/null
+++ b/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 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.recoverablekeystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Helper class with data necessary to recover Keystore on a new device.
+ * It defines UI shown to the user and a way to derive a cryptographic key from user output.
+ *
+ * @hide
+ */
+public final class KeyStoreRecoveryMetadata implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
+ public @interface UserSecretType {
+ }
+
+ /**
+ * Lockscreen secret is required to recover KeyStore.
+ */
+ public static final int TYPE_LOCKSCREEN = 1;
+
+ /**
+ * Custom passphrase, unrelated to lock screen, is required to recover KeyStore.
+ */
+ public static final int TYPE_CUSTOM_PASSWORD = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_PIN, TYPE_PASSWORD, TYPE_PATTERN})
+ public @interface LockScreenUiFormat {
+ }
+
+ /**
+ * Pin with digits only.
+ */
+ public static final int TYPE_PIN = 1;
+
+ /**
+ * Password. String with latin-1 characters only.
+ */
+ public static final int TYPE_PASSWORD = 2;
+
+ /**
+ * Pattern with 3 by 3 grid.
+ */
+ public static final int TYPE_PATTERN = 3;
+
+ @UserSecretType
+ private final int mUserSecretType;
+
+ @LockScreenUiFormat
+ private final int mLockScreenUiFormat;
+
+ /**
+ * Parameters of key derivation function, including algorithm, difficulty, salt.
+ */
+ private KeyDerivationParameters mKeyDerivationParameters;
+ private byte[] mSecret; // Derived from user secret. The field must have limited visibility.
+
+ /**
+ * @param secret Constructor creates a reference to the secret. Caller must use
+ * @link {#clearSecret} to overwrite its value in memory.
+ */
+ public KeyStoreRecoveryMetadata(@UserSecretType int userSecretType,
+ @LockScreenUiFormat int lockScreenUiFormat,
+ @NonNull KeyDerivationParameters keyDerivationParameters, @NonNull byte[] secret) {
+ mUserSecretType = userSecretType;
+ mLockScreenUiFormat = lockScreenUiFormat;
+ mKeyDerivationParameters = Preconditions.checkNotNull(keyDerivationParameters);
+ mSecret = Preconditions.checkNotNull(secret);
+ }
+
+ /**
+ * Specifies UX shown to user during recovery.
+ *
+ * @see KeyStore.TYPE_PIN
+ * @see KeyStore.TYPE_PASSWORD
+ * @see KeyStore.TYPE_PATTERN
+ */
+ public @LockScreenUiFormat int getLockScreenUiFormat() {
+ return mLockScreenUiFormat;
+ }
+
+ /**
+ * Specifies function used to derive symmetric key from user input
+ * Format is defined in separate util class.
+ */
+ public @NonNull KeyDerivationParameters getKeyDerivationParameters() {
+ return mKeyDerivationParameters;
+ }
+
+ /**
+ * Secret string derived from user input.
+ */
+ public @NonNull byte[] getSecret() {
+ return mSecret;
+ }
+
+ /**
+ * @see KeyStore.TYPE_LOCKSCREEN
+ * @see KeyStore.TYPE_CUSTOM_PASSWORD
+ */
+ public @UserSecretType int getUserSecretType() {
+ return mUserSecretType;
+ }
+
+ /**
+ * Removes secret from memory than object is no longer used.
+ * Since finalizer call is not reliable, please use @link {#clearSecret} directly.
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ clearSecret();
+ super.finalize();
+ }
+
+ /**
+ * Fills mSecret with zeroes.
+ */
+ public void clearSecret() {
+ Arrays.fill(mSecret, (byte) 0);
+ }
+
+ public static final Parcelable.Creator<KeyStoreRecoveryMetadata> CREATOR =
+ new Parcelable.Creator<KeyStoreRecoveryMetadata>() {
+ public KeyStoreRecoveryMetadata createFromParcel(Parcel in) {
+ return new KeyStoreRecoveryMetadata(in);
+ }
+
+ public KeyStoreRecoveryMetadata[] newArray(int length) {
+ return new KeyStoreRecoveryMetadata[length];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mUserSecretType);
+ out.writeInt(mLockScreenUiFormat);
+ out.writeTypedObject(mKeyDerivationParameters, flags);
+ out.writeByteArray(mSecret);
+ }
+
+ protected KeyStoreRecoveryMetadata(Parcel in) {
+ mUserSecretType = in.readInt();
+ mLockScreenUiFormat = in.readInt();
+ mKeyDerivationParameters = in.readTypedObject(KeyDerivationParameters.CREATOR);
+ mSecret = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
new file mode 100644
index 00000000..72a138a6
--- /dev/null
+++ b/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2017 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.recoverablekeystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.security.KeyStore;
+import android.util.AndroidException;
+
+import com.android.internal.widget.ILockSettings;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
+ * recovered later.
+ *
+ * @hide
+ */
+public class RecoverableKeyStoreLoader {
+
+ public static final String PERMISSION_RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
+
+ public static final int NO_ERROR = KeyStore.NO_ERROR;
+ public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
+ public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
+ public static final int NO_SNAPSHOT_PENDING_ERROR = 21;
+
+ /**
+ * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+ * can have its own number of user secret guesses allowed.
+ *
+ * @hide
+ */
+ public static final int RATE_LIMIT_EXCEEDED = 21;
+
+ /** Key has been successfully synced. */
+ public static final int RECOVERY_STATUS_SYNCED = 0;
+ /** Waiting for recovery agent to sync the key. */
+ public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+ /** Recovery account is not available. */
+ public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+ /** Key cannot be synced. */
+ public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
+
+ private final ILockSettings mBinder;
+
+ private RecoverableKeyStoreLoader(ILockSettings binder) {
+ mBinder = binder;
+ }
+
+ /** @hide */
+ public static RecoverableKeyStoreLoader getInstance() {
+ ILockSettings lockSettings =
+ ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
+ return new RecoverableKeyStoreLoader(lockSettings);
+ }
+
+ /**
+ * Exceptions returned by {@link RecoverableKeyStoreLoader}.
+ *
+ * @hide
+ */
+ public static class RecoverableKeyStoreLoaderException extends AndroidException {
+ private int mErrorCode;
+
+ /**
+ * Creates new {@link #RecoverableKeyStoreLoaderException} instance from the error code.
+ *
+ * @param errorCode
+ * @hide
+ */
+ public static RecoverableKeyStoreLoaderException fromErrorCode(int errorCode) {
+ return new RecoverableKeyStoreLoaderException(
+ errorCode, getMessageFromErrorCode(errorCode));
+ }
+
+ /**
+ * Creates new {@link #RecoverableKeyStoreLoaderException} from {@link
+ * ServiceSpecificException}.
+ *
+ * @param e exception thrown on service side.
+ * @hide
+ */
+ static RecoverableKeyStoreLoaderException fromServiceSpecificException(
+ ServiceSpecificException e) throws RecoverableKeyStoreLoaderException {
+ throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode);
+ }
+
+ private RecoverableKeyStoreLoaderException(int errorCode, String message) {
+ super(message);
+ }
+
+ /** Returns errorCode. */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** @hide */
+ private static String getMessageFromErrorCode(int errorCode) {
+ switch (errorCode) {
+ case NO_ERROR:
+ return "OK";
+ case SYSTEM_ERROR:
+ return "System error";
+ case UNINITIALIZED_RECOVERY_PUBLIC_KEY:
+ return "Recovery service is not initialized";
+ case RATE_LIMIT_EXCEEDED:
+ return "Rate limit exceeded";
+ default:
+ return String.valueOf("Unknown error code " + errorCode);
+ }
+ }
+ }
+
+ /**
+ * Initializes key recovery service for the calling application. RecoverableKeyStoreLoader
+ * randomly chooses one of the keys from the list and keeps it to use for future key export
+ * operations. Collection of all keys in the list must be signed by the provided {@code
+ * rootCertificateAlias}, which must also be present in the list of root certificates
+ * preinstalled on the device. The random selection allows RecoverableKeyStoreLoader to select
+ * which of a set of remote recovery service devices will be used.
+ *
+ * <p>In addition, RecoverableKeyStoreLoader enforces a delay of three months between
+ * consecutive initialization attempts, to limit the ability of an attacker to often switch
+ * remote recovery devices and significantly increase number of recovery attempts.
+ *
+ * @param rootCertificateAlias alias of a root certificate preinstalled on the device
+ * @param signedPublicKeyList binary blob a list of X509 certificates and signature
+ * @throws RecoverableKeyStoreLoaderException if signature is invalid, or key rotation was rate
+ * limited.
+ * @hide
+ */
+ public void initRecoveryService(
+ @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.initRecoveryService(
+ rootCertificateAlias, signedPublicKeyList, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns data necessary to store all recoverable keys for given account. Key material is
+ * encrypted with user secret and recovery public key.
+ *
+ * @param account specific to Recovery agent.
+ * @return Data necessary to recover keystore.
+ * @hide
+ */
+ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ KeyStoreRecoveryData recoveryData =
+ mBinder.getRecoveryData(account, UserHandle.getCallingUserId());
+ return recoveryData;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+ * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+ * most one registered listener at any time.
+ *
+ * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+ * {@code null}.
+ * @hide
+ */
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+ * version. Version zero is used, if no snapshots were created for the account.
+ *
+ * @return Map from recovery agent accounts to snapshot versions.
+ * @see KeyStoreRecoveryData#getSnapshotVersion
+ * @hide
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<byte[], Integer> result =
+ (Map<byte[], Integer>)
+ mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId());
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Server parameters used to generate new recovery key blobs. This value will be included in
+ * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
+ * in vaultParams {@link #startRecoverySession}
+ *
+ * @param serverParameters included in recovery key blob.
+ * @see #getRecoveryData
+ * @throws RecoverableKeyStoreLoaderException If parameters rotation is rate limited.
+ * @hide
+ */
+ public void setServerParameters(long serverParameters)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setServerParameters(serverParameters, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Updates recovery status for given keys. It is used to notify keystore that key was
+ * successfully stored on the server or there were an error. Application can check this value
+ * using {@code getRecoveyStatus}.
+ *
+ * @param packageName Application whose recoverable keys' statuses are to be updated.
+ * @param aliases List of application-specific key aliases. If the array is empty, updates the
+ * status for all existing recoverable keys.
+ * @param status Status specific to recovery agent.
+ */
+ public void setRecoveryStatus(
+ @NonNull String packageName, @Nullable String[] aliases, int status)
+ throws NameNotFoundException, RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setRecoveryStatus(packageName, aliases, status, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+ * Negative status values are reserved for recovery agent specific codes. List of common codes:
+ *
+ * <ul>
+ * <li>{@link #RECOVERY_STATUS_SYNCED}
+ * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+ * <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+ * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+ * </ul>
+ *
+ * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
+ * {@code null} caller's package will be used.
+ * @return {@code Map} from KeyStore alias to recovery status.
+ * @see #setRecoveryStatus
+ * @hide
+ */
+ public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> result =
+ (Map<String, Integer>)
+ mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId());
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
+ * is necessary to recover data.
+ *
+ * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or {@link
+ * KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD}
+ */
+ public void setRecoverySecretTypes(
+ @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setRecoverySecretTypes(secretTypes, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
+ * necessary to generate KeyStoreRecoveryData.
+ *
+ * @return list of recovery secret types
+ * @see KeyStoreRecoveryData
+ */
+ public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ return mBinder.getRecoverySecretTypes(UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
+ * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
+ * called.
+ *
+ * @return list of recovery secret types
+ */
+ public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ return mBinder.getPendingRecoverySecretTypes(UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Method notifies KeyStore that a user-generated secret is available. This method generates a
+ * symmetric session key which a trusted remote device can use to return a recovery key. Caller
+ * should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value in
+ * memory.
+ *
+ * @param recoverySecret user generated secret together with parameters necessary to regenerate
+ * it on a new device.
+ */
+ public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.recoverySecretAvailable(recoverySecret, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Initializes recovery session and returns a blob with proof of recovery secrets possession.
+ * The method generates symmetric key for a session, which trusted remote device can use to
+ * return recovery key.
+ *
+ * @param sessionId ID for recovery session.
+ * @param verifierPublicKey Certificate with Public key used to create the recovery blob on the
+ * source device. Keystore will verify the certificate using root of trust.
+ * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
+ * Used to limit number of guesses.
+ * @param vaultChallenge Data passed from server for this recovery session and used to prevent
+ * replay attacks
+ * @param secrets Secrets provided by user, the method only uses type and secret fields.
+ * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains
+ * a proof of user secrets, session symmetric key and parameters necessary to identify the
+ * counter with the number of failed recovery attempts.
+ */
+ public @NonNull byte[] startRecoverySession(
+ @NonNull String sessionId,
+ @NonNull byte[] verifierPublicKey,
+ @NonNull byte[] vaultParams,
+ @NonNull byte[] vaultChallenge,
+ @NonNull List<KeyStoreRecoveryMetadata> secrets)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ byte[] recoveryClaim =
+ mBinder.startRecoverySession(
+ sessionId,
+ verifierPublicKey,
+ vaultParams,
+ vaultChallenge,
+ secrets,
+ UserHandle.getCallingUserId());
+ return recoveryClaim;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Imports keys.
+ *
+ * @param sessionId Id for recovery session, same as in
+ * {@link #startRecoverySession(String, byte[], byte[], byte[], List)} on}.
+ * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
+ * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
+ * and session. KeyStore only uses package names from the application info in {@link
+ * KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
+ * @return Map from alias to raw key material.
+ */
+ public Map<String, byte[]> recoverKeys(
+ @NonNull String sessionId,
+ @NonNull byte[] recoveryKeyBlob,
+ @NonNull List<KeyEntryRecoveryData> applicationKeys)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ return (Map<String, byte[]>) mBinder.recoverKeys(
+ sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
+ * raw material of the key.
+ *
+ * @throws RecoverableKeyStoreLoaderException if an error occurred generating and storing the
+ * key.
+ */
+ public byte[] generateAndStoreKey(String alias) throws RecoverableKeyStoreLoaderException {
+ try {
+ return mBinder.generateAndStoreKey(alias);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+}