Add an optional metadata blob for recoverable application keys

This metadata, if present, will be authenticated (but unencrypted)
together with the application key material.

Bug: 112191661
Test: atest FrameworksCoreTests:android.security.keystore.recovery
      atest FrameworksServicesTests:com.android.server.locksettings.recoverablekeystore
      atest -m RecoveryControllerHostTest RecoverableKeyStoreEndtoEndHostTest RecoverySessionHostTest

Change-Id: I2846952758a2c1a7b1f0849e1adda1f05a3e305e
This commit is contained in:
Bo Zhu
2019-01-03 14:04:58 -08:00
parent 252e8d0447
commit c704834cb6
12 changed files with 358 additions and 42 deletions

View File

@@ -5092,7 +5092,8 @@ package android.security.keystore.recovery {
public class RecoveryController {
method public android.security.keystore.recovery.RecoverySession createRecoverySession();
method public java.security.Key generateKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
method public deprecated java.security.Key generateKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
method public java.security.Key generateKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
method public java.util.List<java.lang.String> getAliases() throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public static android.security.keystore.recovery.RecoveryController getInstance(android.content.Context);
method public java.security.Key getKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException, java.security.UnrecoverableKeyException;
@@ -5100,7 +5101,8 @@ package android.security.keystore.recovery {
method public int[] getRecoverySecretTypes() throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public int getRecoveryStatus(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public java.util.Map<java.lang.String, java.security.cert.X509Certificate> getRootCertificates();
method public java.security.Key importKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
method public deprecated java.security.Key importKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
method public java.security.Key importKey(java.lang.String, byte[], byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
method public void initRecoveryService(java.lang.String, byte[], byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
method public static boolean isRecoverableKeyStoreEnabled(android.content.Context);
method public void removeKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
@@ -5127,6 +5129,7 @@ package android.security.keystore.recovery {
method public int describeContents();
method public java.lang.String getAlias();
method public byte[] getEncryptedKeyMaterial();
method public byte[] getMetadata();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.security.keystore.recovery.WrappedApplicationKey> CREATOR;
}
@@ -5136,6 +5139,7 @@ package android.security.keystore.recovery {
method public android.security.keystore.recovery.WrappedApplicationKey build();
method public android.security.keystore.recovery.WrappedApplicationKey.Builder setAlias(java.lang.String);
method public android.security.keystore.recovery.WrappedApplicationKey.Builder setEncryptedKeyMaterial(byte[]);
method public android.security.keystore.recovery.WrappedApplicationKey.Builder setMetadata(byte[]);
}
}

View File

@@ -533,7 +533,10 @@ public class RecoveryController {
* service.
* @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
* screen is required to generate recoverable keys.
*
* @deprecated Use the method {@link #generateKey(String, byte[])} instead.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public @NonNull Key generateKey(@NonNull String alias) throws InternalRecoveryServiceException,
LockScreenRequiredException {
@@ -555,6 +558,47 @@ public class RecoveryController {
}
}
/**
* Generates a recoverable key with the given {@code alias} and {@code metadata}.
*
* <p>The metadata should contain any data that needs to be cryptographically bound to the
* generated key, but does not need to be encrypted by the key. For example, the metadata can
* be a byte string describing the algorithms and non-secret parameters to be used with the
* key. The supplied metadata can later be obtained via
* {@link WrappedApplicationKey#getMetadata()}.
*
* <p>During the key recovery process, the same metadata has to be supplied via
* {@link WrappedApplicationKey.Builder#setMetadata(byte[])}; otherwise, the recovery process
* will fail due to the checking of the cryptographic binding. This can help prevent
* potential attacks that try to swap key materials on the backup server and trick the
* application to use keys with different algorithms or parameters.
*
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
* @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
* screen is required to generate recoverable keys.
*/
@RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public @NonNull Key generateKey(@NonNull String alias, @Nullable byte[] metadata)
throws InternalRecoveryServiceException, LockScreenRequiredException {
try {
String grantAlias = mBinder.generateKeyWithMetadata(alias, metadata);
if (grantAlias == null) {
throw new InternalRecoveryServiceException("null grant alias");
}
return getKeyFromGrant(grantAlias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (UnrecoverableKeyException e) {
throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_INSECURE_USER) {
throw new LockScreenRequiredException(e.getMessage());
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code
* keyBytes}.
@@ -564,7 +608,9 @@ public class RecoveryController {
* @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
* screen is required to generate recoverable keys.
*
* @deprecated Use the method {@link #importKey(String, byte[], byte[])} instead.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public @NonNull Key importKey(@NonNull String alias, @NonNull byte[] keyBytes)
throws InternalRecoveryServiceException, LockScreenRequiredException {
@@ -586,6 +632,49 @@ public class RecoveryController {
}
}
/**
* Imports a recoverable 256-bit AES key with the given {@code alias}, the raw bytes {@code
* keyBytes}, and the {@code metadata}.
*
* <p>The metadata should contain any data that needs to be cryptographically bound to the
* imported key, but does not need to be encrypted by the key. For example, the metadata can
* be a byte string describing the algorithms and non-secret parameters to be used with the
* key. The supplied metadata can later be obtained via
* {@link WrappedApplicationKey#getMetadata()}.
*
* <p>During the key recovery process, the same metadata has to be supplied via
* {@link WrappedApplicationKey.Builder#setMetadata(byte[])}; otherwise, the recovery process
* will fail due to the checking of the cryptographic binding. This can help prevent
* potential attacks that try to swap key materials on the backup server and trick the
* application to use keys with different algorithms or parameters.
*
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
* @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
* screen is required to generate recoverable keys.
*/
@RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public @NonNull Key importKey(@NonNull String alias, @NonNull byte[] keyBytes,
@Nullable byte[] metadata)
throws InternalRecoveryServiceException, LockScreenRequiredException {
try {
String grantAlias = mBinder.importKeyWithMetadata(alias, keyBytes, metadata);
if (grantAlias == null) {
throw new InternalRecoveryServiceException("Null grant alias");
}
return getKeyFromGrant(grantAlias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (UnrecoverableKeyException e) {
throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_INSECURE_USER) {
throw new LockScreenRequiredException(e.getMessage());
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Gets a key called {@code alias} from the recoverable key store.
*

View File

@@ -17,6 +17,7 @@
package android.security.keystore.recovery;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,6 +42,8 @@ public final class WrappedApplicationKey implements Parcelable {
private String mAlias;
// The only supported format is AES-256 symmetric key.
private byte[] mEncryptedKeyMaterial;
// The optional metadata that's authenticated (but unencrypted) with the key material.
private byte[] mMetadata;
// IMPORTANT! PLEASE READ!
// -----------------------
@@ -80,12 +83,22 @@ public final class WrappedApplicationKey implements Parcelable {
* @param encryptedKeyMaterial The key material
* @return This builder
*/
public Builder setEncryptedKeyMaterial(@NonNull byte[] encryptedKeyMaterial) {
mInstance.mEncryptedKeyMaterial = encryptedKeyMaterial;
return this;
}
/**
* Sets the metadata that is authenticated (but unecrypted) with the key material.
*
* @param metadata The metadata
* @return This builder
*/
public Builder setMetadata(@Nullable byte[] metadata) {
mInstance.mMetadata = metadata;
return this;
}
/**
* Creates a new {@link WrappedApplicationKey} instance.
*
@@ -102,9 +115,10 @@ public final class WrappedApplicationKey implements Parcelable {
private WrappedApplicationKey() { }
/**
* Deprecated - consider using Builder.
* @deprecated Use the builder instead.
* @hide
*/
@Deprecated
public WrappedApplicationKey(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
mAlias = Preconditions.checkNotNull(alias);
mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
@@ -124,6 +138,11 @@ public final class WrappedApplicationKey implements Parcelable {
return mEncryptedKeyMaterial;
}
/** The metadata with the key. */
public @Nullable byte[] getMetadata() {
return mMetadata;
}
public static final Parcelable.Creator<WrappedApplicationKey> CREATOR =
new Parcelable.Creator<WrappedApplicationKey>() {
public WrappedApplicationKey createFromParcel(Parcel in) {
@@ -139,6 +158,7 @@ public final class WrappedApplicationKey implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeString(mAlias);
out.writeByteArray(mEncryptedKeyMaterial);
out.writeByteArray(mMetadata);
}
/**
@@ -147,6 +167,10 @@ public final class WrappedApplicationKey implements Parcelable {
protected WrappedApplicationKey(Parcel in) {
mAlias = in.readString();
mEncryptedKeyMaterial = in.createByteArray();
// Check if there is still data to be read.
if (in.dataAvail() > 0) {
mMetadata = in.createByteArray();
}
}
@Override

View File

@@ -62,7 +62,9 @@ interface ILockSettings {
in byte[] recoveryServiceCertFile, in byte[] recoveryServiceSigFile);
KeyChainSnapshot getKeyChainSnapshot();
String generateKey(String alias);
String generateKeyWithMetadata(String alias, in byte[] metadata);
String importKey(String alias, in byte[] keyBytes);
String importKeyWithMetadata(String alias, in byte[] keyBytes, in byte[] metadata);
String getKey(String alias);
void removeKey(String alias);
void setSnapshotCreatedPendingIntent(in PendingIntent intent);

View File

@@ -44,6 +44,7 @@ public class KeyChainSnapshotTest {
private static final int USER_SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN;
private static final String KEY_ALIAS = "steph";
private static final byte[] KEY_MATERIAL = new byte[] { 3, 5, 7, 9, 1 };
private static final byte[] KEY_METADATA = new byte[] { 5, 3, 11, 13 };
private static final CertPath CERT_PATH = TestData.getThmCertPath();
@Test
@@ -99,6 +100,7 @@ public class KeyChainSnapshotTest {
WrappedApplicationKey wrappedApplicationKey = snapshot.getWrappedApplicationKeys().get(0);
assertEquals(KEY_ALIAS, wrappedApplicationKey.getAlias());
assertArrayEquals(KEY_MATERIAL, wrappedApplicationKey.getEncryptedKeyMaterial());
assertArrayEquals(KEY_METADATA, wrappedApplicationKey.getMetadata());
}
@Test
@@ -165,6 +167,7 @@ public class KeyChainSnapshotTest {
WrappedApplicationKey wrappedApplicationKey = snapshot.getWrappedApplicationKeys().get(0);
assertEquals(KEY_ALIAS, wrappedApplicationKey.getAlias());
assertArrayEquals(KEY_MATERIAL, wrappedApplicationKey.getEncryptedKeyMaterial());
assertArrayEquals(KEY_METADATA, wrappedApplicationKey.getMetadata());
}
private static KeyChainSnapshot createKeyChainSnapshot() throws Exception {
@@ -196,6 +199,7 @@ public class KeyChainSnapshotTest {
return new WrappedApplicationKey.Builder()
.setAlias(KEY_ALIAS)
.setEncryptedKeyMaterial(KEY_MATERIAL)
.setMetadata(KEY_METADATA)
.build();
}

View File

@@ -34,6 +34,7 @@ public class WrappedApplicationKeyTest {
private static final String ALIAS = "karlin";
private static final byte[] KEY_MATERIAL = new byte[] { 0, 1, 2, 3, 4 };
private static final byte[] METADATA = new byte[] {3, 2, 1, 0};
private Parcel mParcel;
@@ -57,9 +58,19 @@ public class WrappedApplicationKeyTest {
assertArrayEquals(KEY_MATERIAL, buildTestKey().getEncryptedKeyMaterial());
}
@Test
public void build_setsMetadata_nonNull() {
assertArrayEquals(METADATA, buildTestKeyWithMetadata(METADATA).getMetadata());
}
@Test
public void build_setsMetadata_null() {
assertArrayEquals(null, buildTestKeyWithMetadata(null).getMetadata());
}
@Test
public void writeToParcel_writesAliasToParcel() {
buildTestKey().writeToParcel(mParcel, /*flags=*/ 0);
buildTestKeyWithMetadata(/*metadata=*/ null).writeToParcel(mParcel, /*flags=*/ 0);
mParcel.setDataPosition(0);
WrappedApplicationKey readFromParcel =
@@ -69,7 +80,7 @@ public class WrappedApplicationKeyTest {
@Test
public void writeToParcel_writesKeyMaterial() {
buildTestKey().writeToParcel(mParcel, /*flags=*/ 0);
buildTestKeyWithMetadata(/*metadata=*/ null).writeToParcel(mParcel, /*flags=*/ 0);
mParcel.setDataPosition(0);
WrappedApplicationKey readFromParcel =
@@ -77,10 +88,48 @@ public class WrappedApplicationKeyTest {
assertArrayEquals(KEY_MATERIAL, readFromParcel.getEncryptedKeyMaterial());
}
@Test
public void writeToParcel_writesMetadata_nonNull() {
buildTestKeyWithMetadata(METADATA).writeToParcel(mParcel, /*flags=*/ 0);
mParcel.setDataPosition(0);
WrappedApplicationKey readFromParcel =
WrappedApplicationKey.CREATOR.createFromParcel(mParcel);
assertArrayEquals(METADATA, readFromParcel.getMetadata());
}
@Test
public void writeToParcel_writesMetadata_null() {
buildTestKeyWithMetadata(/*metadata=*/ null).writeToParcel(mParcel, /*flags=*/ 0);
mParcel.setDataPosition(0);
WrappedApplicationKey readFromParcel =
WrappedApplicationKey.CREATOR.createFromParcel(mParcel);
assertArrayEquals(null, readFromParcel.getMetadata());
}
@Test
public void writeToParcel_writesMetadata_absent() {
buildTestKey().writeToParcel(mParcel, /*flags=*/ 0);
mParcel.setDataPosition(0);
WrappedApplicationKey readFromParcel =
WrappedApplicationKey.CREATOR.createFromParcel(mParcel);
assertArrayEquals(null, readFromParcel.getMetadata());
}
private WrappedApplicationKey buildTestKey() {
return new WrappedApplicationKey.Builder()
.setAlias(ALIAS)
.setEncryptedKeyMaterial(KEY_MATERIAL)
.build();
}
private WrappedApplicationKey buildTestKeyWithMetadata(byte[] metadata) {
return new WrappedApplicationKey.Builder()
.setAlias(ALIAS)
.setEncryptedKeyMaterial(KEY_MATERIAL)
.setMetadata(metadata)
.build();
}
}

View File

@@ -21,10 +21,10 @@ import static android.Manifest.permission.READ_CONTACTS;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
@@ -81,9 +81,9 @@ import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.UserNotAuthenticatedException;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryCertPath;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
@@ -109,9 +109,9 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.wm.WindowManagerInternal;
import libcore.util.HexEncoding;
@@ -130,8 +130,8 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -2103,10 +2103,23 @@ public class LockSettingsService extends ILockSettings.Stub {
}
@Override
public @Nullable String importKey(@NonNull String alias, byte[] keyBytes) throws RemoteException {
public @Nullable String generateKeyWithMetadata(
@NonNull String alias, @Nullable byte[] metadata) throws RemoteException {
return mRecoverableKeyStoreManager.generateKeyWithMetadata(alias, metadata);
}
@Override
public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
throws RemoteException {
return mRecoverableKeyStoreManager.importKey(alias, keyBytes);
}
@Override
public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes,
@Nullable byte[] metadata) throws RemoteException {
return mRecoverableKeyStoreManager.importKeyWithMetadata(alias, keyBytes, metadata);
}
@Override
public @Nullable String getKey(@NonNull String alias) throws RemoteException {
return mRecoverableKeyStoreManager.getKey(alias);

View File

@@ -20,8 +20,8 @@ import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CE
import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED;
@@ -35,12 +35,12 @@ import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.security.KeyStore;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryCertPath;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.KeyStore;
import android.util.ArrayMap;
import android.util.Log;
@@ -59,7 +59,6 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnaps
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
@@ -70,7 +69,6 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -674,14 +672,36 @@ public class RecoverableKeyStoreManager {
* Generates a key named {@code alias} in caller's namespace.
* The key is stored in system service keystore namespace.
*
* @param alias the alias provided by caller as a reference to the key.
* @return grant alias, which caller can use to access the key.
* @throws RemoteException if certain internal errors occur.
*
* @deprecated Use {@link #generateKeyWithMetadata(String, byte[])} instead.
*/
@Deprecated
public String generateKey(@NonNull String alias) throws RemoteException {
return generateKeyWithMetadata(alias, /*metadata=*/ null);
}
/**
* Generates a key named {@code alias} with the {@code metadata} in caller's namespace.
* The key is stored in system service keystore namespace.
*
* @param alias the alias provided by caller as a reference to the key.
* @param metadata the optional metadata blob that will authenticated (but unencrypted) together
* with the key material when the key is uploaded to cloud.
* @return grant alias, which caller can use to access the key.
* @throws RemoteException if certain internal errors occur.
*/
public String generateKeyWithMetadata(@NonNull String alias, @Nullable byte[] metadata)
throws RemoteException {
checkRecoverKeyStorePermission();
Preconditions.checkNotNull(alias, "alias is null");
int uid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
// TODO: Include metadata in the processes of authentication and storage
PlatformEncryptionKey encryptionKey;
try {
encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
@@ -713,10 +733,30 @@ public class RecoverableKeyStoreManager {
* @return grant alias, which caller can use to access the key.
* @throws RemoteException if the given key is invalid or some internal errors occur.
*
* @deprecated Use {{@link #importKeyWithMetadata(String, byte[], byte[])}} instead.
*
* @hide
*/
@Deprecated
public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
throws RemoteException {
return importKeyWithMetadata(alias, keyBytes, /*metadata=*/ null);
}
/**
* Imports a 256-bit AES-GCM key named {@code alias} with the given {@code metadata}. The key is
* stored in system service keystore namespace.
*
* @param alias the alias provided by caller as a reference to the key.
* @param keyBytes the raw bytes of the 256-bit AES key.
* @param metadata the metadata to be authenticated (but unencrypted) together with the key.
* @return grant alias, which caller can use to access the key.
* @throws RemoteException if the given key is invalid or some internal errors occur.
*
* @hide
*/
public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes,
@Nullable byte[] metadata) throws RemoteException {
checkRecoverKeyStorePermission();
Preconditions.checkNotNull(alias, "alias is null");
Preconditions.checkNotNull(keyBytes, "keyBytes is null");
@@ -728,6 +768,8 @@ public class RecoverableKeyStoreManager {
+ " bits.");
}
// TODO: Include metadata in the processes of authentication and storage
int uid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();

View File

@@ -23,19 +23,18 @@ import static com.android.server.locksettings.recoverablekeystore.serialization.
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS;
import static com.android.server.locksettings.recoverablekeystore.serialization
.KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_METADATA;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION;
@@ -49,6 +48,9 @@ import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Base64;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -59,9 +61,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Deserializes a {@link android.security.keystore.recovery.KeyChainSnapshot} instance from XML.
*/
@@ -191,6 +190,10 @@ public class KeyChainSnapshotDeserializer {
builder.setEncryptedKeyMaterial(readBlobTag(parser, TAG_KEY_MATERIAL));
break;
case TAG_KEY_METADATA:
builder.setMetadata(readBlobTag(parser, TAG_KEY_METADATA));
break;
default:
throw new KeyChainSnapshotParserException(String.format(
Locale.US, "Unexpected tag %s in wrappedApplicationKey", name));

View File

@@ -52,6 +52,7 @@ class KeyChainSnapshotSchema {
static final String TAG_APPLICATION_KEY = "applicationKey";
static final String TAG_ALIAS = "alias";
static final String TAG_KEY_MATERIAL = "keyMaterial";
static final String TAG_KEY_METADATA = "keyMetadata";
// Statics only
private KeyChainSnapshotSchema() {}

View File

@@ -24,25 +24,24 @@ import static com.android.server.locksettings.recoverablekeystore.serialization.
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS;
import static com.android.server.locksettings.recoverablekeystore.serialization
.KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_METADATA;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_TRUSTED_HARDWARE_CERT_PATH;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_USER_SECRET_TYPE;
import android.annotation.Nullable;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
@@ -103,6 +102,7 @@ public class KeyChainSnapshotSerializer {
XmlSerializer xmlSerializer, WrappedApplicationKey applicationKey) throws IOException {
writePropertyTag(xmlSerializer, TAG_ALIAS, applicationKey.getAlias());
writePropertyTag(xmlSerializer, TAG_KEY_MATERIAL, applicationKey.getEncryptedKeyMaterial());
writePropertyTag(xmlSerializer, TAG_KEY_METADATA, applicationKey.getMetadata());
}
private static void writeKeyChainProtectionParams(
@@ -181,8 +181,11 @@ public class KeyChainSnapshotSerializer {
}
private static void writePropertyTag(
XmlSerializer xmlSerializer, String propertyName, byte[] propertyValue)
XmlSerializer xmlSerializer, String propertyName, @Nullable byte[] propertyValue)
throws IOException {
if (propertyValue == null) {
return;
}
xmlSerializer.startTag(NAMESPACE, propertyName);
xmlSerializer.text(Base64.encodeToString(propertyValue, /*flags=*/ Base64.DEFAULT));
xmlSerializer.endTag(NAMESPACE, propertyName);

View File

@@ -55,12 +55,15 @@ public class KeyChainSnapshotSerializerTest {
private static final String TEST_KEY_1_ALIAS = "key1";
private static final byte[] TEST_KEY_1_BYTES = new byte[] { 66, 77, 88 };
private static final byte[] TEST_KEY_1_METADATA = new byte[] { 89, 87 };
private static final String TEST_KEY_2_ALIAS = "key2";
private static final byte[] TEST_KEY_2_BYTES = new byte[] { 99, 33, 11 };
private static final byte[] TEST_KEY_2_METADATA = new byte[] {};
private static final String TEST_KEY_3_ALIAS = "key3";
private static final byte[] TEST_KEY_3_BYTES = new byte[] { 2, 8, 100 };
private static final byte[] TEST_KEY_3_METADATA = new byte[] { 121 };
@Test
public void roundTrip_persistsCounterId() throws Exception {
@@ -143,6 +146,17 @@ public class KeyChainSnapshotSerializerTest {
assertThat(roundTripKeys().get(0).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_1_BYTES);
}
@Test
public void roundTripKeys_0_persistsKeyMetadata_absent() throws Exception {
assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(0).getMetadata()).isEqualTo(null);
}
@Test
public void roundTripKeys_0_persistsKeyMetadata_present() throws Exception {
assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(0).getMetadata())
.isEqualTo(TEST_KEY_1_METADATA);
}
@Test
public void roundTripKeys_1_persistsAlias() throws Exception {
assertThat(roundTripKeys().get(1).getAlias()).isEqualTo(TEST_KEY_2_ALIAS);
@@ -153,6 +167,17 @@ public class KeyChainSnapshotSerializerTest {
assertThat(roundTripKeys().get(1).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_2_BYTES);
}
@Test
public void roundTripKeys_1_persistsKeyMetadata_absent() throws Exception {
assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(1).getMetadata()).isEqualTo(null);
}
@Test
public void roundTripKeys_1_persistsKeyMetadata_present() throws Exception {
assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(1).getMetadata())
.isEqualTo(TEST_KEY_2_METADATA);
}
@Test
public void roundTripKeys_2_persistsAlias() throws Exception {
assertThat(roundTripKeys().get(2).getAlias()).isEqualTo(TEST_KEY_3_ALIAS);
@@ -164,28 +189,74 @@ public class KeyChainSnapshotSerializerTest {
}
@Test
public void serialize_doesNotThrowForTestSnapshot() throws Exception {
public void roundTripKeys_2_persistsKeyMetadata_absent() throws Exception {
assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(2).getMetadata()).isEqualTo(null);
}
@Test
public void roundTripKeys_2_persistsKeyMetadata_present() throws Exception {
assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(2).getMetadata())
.isEqualTo(TEST_KEY_3_METADATA);
}
@Test
public void serialize_doesNotThrowForTestSnapshotWithoutKeyMetadata() throws Exception {
KeyChainSnapshotSerializer.serialize(
createTestKeyChainSnapshot(), new ByteArrayOutputStream());
createTestKeyChainSnapshot(/*withKeyMetadata=*/ false),
new ByteArrayOutputStream());
}
@Test
public void serialize_doesNotThrowForTestSnapshotWithKeyMetadata() throws Exception {
KeyChainSnapshotSerializer.serialize(
createTestKeyChainSnapshotWithKeyMetadata(), new ByteArrayOutputStream());
}
private static List<WrappedApplicationKey> roundTripKeys() throws Exception {
return roundTrip().getWrappedApplicationKeys();
}
private static List<WrappedApplicationKey> roundTripKeys(boolean withKeyMetadata)
throws Exception {
return roundTrip(withKeyMetadata).getWrappedApplicationKeys();
}
private static KeyChainProtectionParams roundTripParams() throws Exception {
return roundTrip().getKeyChainProtectionParams().get(0);
return roundTrip(/*withKeyMetadata=*/ false).getKeyChainProtectionParams().get(0);
}
public static KeyChainSnapshot roundTrip() throws Exception {
KeyChainSnapshot snapshot = createTestKeyChainSnapshot();
return roundTrip(/*withKeyMetadata=*/ false);
}
public static KeyChainSnapshot roundTrip(boolean withKeyMetadata) throws Exception {
KeyChainSnapshot snapshot = createTestKeyChainSnapshot(withKeyMetadata);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
KeyChainSnapshotSerializer.serialize(snapshot, byteArrayOutputStream);
return KeyChainSnapshotDeserializer.deserialize(
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
}
private static KeyChainSnapshot createTestKeyChainSnapshot() throws Exception {
private static KeyChainSnapshot createTestKeyChainSnapshot(boolean withKeyMetadata)
throws Exception {
KeyChainSnapshot.Builder builder = new KeyChainSnapshot.Builder()
.setCounterId(COUNTER_ID)
.setSnapshotVersion(SNAPSHOT_VERSION)
.setServerParams(SERVER_PARAMS)
.setMaxAttempts(MAX_ATTEMPTS)
.setEncryptedRecoveryKeyBlob(KEY_BLOB)
.setKeyChainProtectionParams(createKeyChainProtectionParamsList())
.setTrustedHardwareCertPath(CERT_PATH);
if (withKeyMetadata) {
builder.setWrappedApplicationKeys(createKeysWithMetadata());
} else {
builder.setWrappedApplicationKeys(createKeysWithoutMetadata());
}
return builder.build();
}
private static KeyChainSnapshot createTestKeyChainSnapshotWithKeyMetadata()
throws Exception {
return new KeyChainSnapshot.Builder()
.setCounterId(COUNTER_ID)
.setSnapshotVersion(SNAPSHOT_VERSION)
@@ -193,16 +264,24 @@ public class KeyChainSnapshotSerializerTest {
.setMaxAttempts(MAX_ATTEMPTS)
.setEncryptedRecoveryKeyBlob(KEY_BLOB)
.setKeyChainProtectionParams(createKeyChainProtectionParamsList())
.setWrappedApplicationKeys(createKeys())
.setWrappedApplicationKeys(createKeysWithMetadata())
.setTrustedHardwareCertPath(CERT_PATH)
.build();
}
private static List<WrappedApplicationKey> createKeys() {
private static List<WrappedApplicationKey> createKeysWithoutMetadata() {
ArrayList<WrappedApplicationKey> keyList = new ArrayList<>();
keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES));
keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES));
keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES));
keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES, /*metadata=*/ null));
keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES, /*metadata=*/ null));
keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES, /*metadata=*/ null));
return keyList;
}
private static List<WrappedApplicationKey> createKeysWithMetadata() {
ArrayList<WrappedApplicationKey> keyList = new ArrayList<>();
keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES, TEST_KEY_1_METADATA));
keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES, TEST_KEY_2_METADATA));
keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES, TEST_KEY_3_METADATA));
return keyList;
}
@@ -221,10 +300,13 @@ public class KeyChainSnapshotSerializerTest {
return keyChainProtectionParamsList;
}
private static WrappedApplicationKey createKey(String alias, byte[] bytes) {
return new WrappedApplicationKey.Builder()
private static WrappedApplicationKey createKey(String alias, byte[] bytes, byte[] metadata) {
WrappedApplicationKey.Builder builder = new WrappedApplicationKey.Builder()
.setAlias(alias)
.setEncryptedKeyMaterial(bytes)
.build();
.setEncryptedKeyMaterial(bytes);
if (metadata != null) {
builder.setMetadata(metadata);
}
return builder.build();
}
}