diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-01-03 13:39:41 -0500 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-01-03 13:39:41 -0500 |
commit | 98fe7819c6d14f4f464a5cac047f9e82dee5da58 (patch) | |
tree | a6b8b93eb21e205b27590ab5e2a1fb9efe27f892 /android/security | |
parent | 4217cf85c20565a3446a662a7f07f26137b26b7f (diff) | |
download | android-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')
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); + } + } +} |