diff options
28 files changed, 1863 insertions, 59 deletions
diff --git a/core/java/android/security/keystore/BackwardsCompat.java b/core/java/android/security/keystore/BackwardsCompat.java new file mode 100644 index 000000000000..24921f03f136 --- /dev/null +++ b/core/java/android/security/keystore/BackwardsCompat.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 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 java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * Helpers for converting classes between old and new API, so we can preserve backwards + * compatibility while teamfooding. This will be removed soon. + * + * @hide + */ +class BackwardsCompat { + + + static KeychainProtectionParams toLegacyKeychainProtectionParams( + android.security.keystore.recovery.KeychainProtectionParams keychainProtectionParams + ) { + return new KeychainProtectionParams.Builder() + .setUserSecretType(keychainProtectionParams.getUserSecretType()) + .setSecret(keychainProtectionParams.getSecret()) + .setLockScreenUiFormat(keychainProtectionParams.getLockScreenUiFormat()) + .setKeyDerivationParams( + toLegacyKeyDerivationParams( + keychainProtectionParams.getKeyDerivationParams())) + .build(); + } + + static KeyDerivationParams toLegacyKeyDerivationParams( + android.security.keystore.recovery.KeyDerivationParams keyDerivationParams + ) { + return new KeyDerivationParams( + keyDerivationParams.getAlgorithm(), keyDerivationParams.getSalt()); + } + + static WrappedApplicationKey toLegacyWrappedApplicationKey( + android.security.keystore.recovery.WrappedApplicationKey wrappedApplicationKey + ) { + return new WrappedApplicationKey.Builder() + .setAlias(wrappedApplicationKey.getAlias()) + .setEncryptedKeyMaterial(wrappedApplicationKey.getEncryptedKeyMaterial()) + .build(); + } + + static android.security.keystore.recovery.KeyDerivationParams fromLegacyKeyDerivationParams( + KeyDerivationParams keyDerivationParams + ) { + return new android.security.keystore.recovery.KeyDerivationParams( + keyDerivationParams.getAlgorithm(), keyDerivationParams.getSalt()); + } + + static android.security.keystore.recovery.WrappedApplicationKey fromLegacyWrappedApplicationKey( + WrappedApplicationKey wrappedApplicationKey + ) { + return new android.security.keystore.recovery.WrappedApplicationKey.Builder() + .setAlias(wrappedApplicationKey.getAlias()) + .setEncryptedKeyMaterial(wrappedApplicationKey.getEncryptedKeyMaterial()) + .build(); + } + + static List<android.security.keystore.recovery.WrappedApplicationKey> + fromLegacyWrappedApplicationKeys(List<WrappedApplicationKey> wrappedApplicationKeys + ) { + return map(wrappedApplicationKeys, BackwardsCompat::fromLegacyWrappedApplicationKey); + } + + static List<android.security.keystore.recovery.KeychainProtectionParams> + fromLegacyKeychainProtectionParams( + List<KeychainProtectionParams> keychainProtectionParams) { + return map(keychainProtectionParams, BackwardsCompat::fromLegacyKeychainProtectionParam); + } + + static android.security.keystore.recovery.KeychainProtectionParams + fromLegacyKeychainProtectionParam(KeychainProtectionParams keychainProtectionParams) { + return new android.security.keystore.recovery.KeychainProtectionParams.Builder() + .setUserSecretType(keychainProtectionParams.getUserSecretType()) + .setSecret(keychainProtectionParams.getSecret()) + .setLockScreenUiFormat(keychainProtectionParams.getLockScreenUiFormat()) + .setKeyDerivationParams( + fromLegacyKeyDerivationParams( + keychainProtectionParams.getKeyDerivationParams())) + .build(); + } + + static KeychainSnapshot toLegacyKeychainSnapshot( + android.security.keystore.recovery.KeychainSnapshot keychainSnapshot + ) { + return new KeychainSnapshot.Builder() + .setCounterId(keychainSnapshot.getCounterId()) + .setEncryptedRecoveryKeyBlob(keychainSnapshot.getEncryptedRecoveryKeyBlob()) + .setTrustedHardwarePublicKey(keychainSnapshot.getTrustedHardwarePublicKey()) + .setSnapshotVersion(keychainSnapshot.getSnapshotVersion()) + .setMaxAttempts(keychainSnapshot.getMaxAttempts()) + .setServerParams(keychainSnapshot.getServerParams()) + .setKeychainProtectionParams( + map(keychainSnapshot.getKeychainProtectionParams(), + BackwardsCompat::toLegacyKeychainProtectionParams)) + .setWrappedApplicationKeys( + map(keychainSnapshot.getWrappedApplicationKeys(), + BackwardsCompat::toLegacyWrappedApplicationKey)) + .build(); + } + + static <A, B> List<B> map(List<A> as, Function<A, B> f) { + ArrayList<B> bs = new ArrayList<>(as.size()); + for (A a : as) { + bs.add(f.apply(a)); + } + return bs; + } +} diff --git a/core/java/android/security/keystore/KeyDerivationParams.java b/core/java/android/security/keystore/KeyDerivationParams.java index b702accffba1..b19cee2d31a4 100644 --- a/core/java/android/security/keystore/KeyDerivationParams.java +++ b/core/java/android/security/keystore/KeyDerivationParams.java @@ -61,7 +61,7 @@ public final class KeyDerivationParams implements Parcelable { return new KeyDerivationParams(ALGORITHM_SHA256, salt); } - private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) { + KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) { mAlgorithm = algorithm; mSalt = Preconditions.checkNotNull(salt); } diff --git a/core/java/android/security/keystore/RecoveryController.java b/core/java/android/security/keystore/RecoveryController.java index 87283cbd75ab..8be6d5263c53 100644 --- a/core/java/android/security/keystore/RecoveryController.java +++ b/core/java/android/security/keystore/RecoveryController.java @@ -167,7 +167,7 @@ public class RecoveryController { public @NonNull KeychainSnapshot getRecoveryData(@NonNull byte[] account) throws InternalRecoveryServiceException { try { - return mBinder.getRecoveryData(account); + return BackwardsCompat.toLegacyKeychainSnapshot(mBinder.getRecoveryData(account)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { @@ -360,28 +360,6 @@ public class RecoveryController { } /** - * 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 KeychainProtectionParams#clearSecret} to override the secret value in - * memory. - * - * @param recoverySecret user generated secret together with parameters necessary to regenerate - * it on a new device. - * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery - * service. - */ - public void recoverySecretAvailable(@NonNull KeychainProtectionParams recoverySecret) - throws InternalRecoveryServiceException { - try { - mBinder.recoverySecretAvailable(recoverySecret); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (ServiceSpecificException e) { - throw wrapUnexpectedServiceSpecificException(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. @@ -417,7 +395,7 @@ public class RecoveryController { verifierPublicKey, vaultParams, vaultChallenge, - secrets); + BackwardsCompat.fromLegacyKeychainProtectionParams(secrets)); return new RecoveryClaim(recoverySession, recoveryClaim); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -451,7 +429,9 @@ public class RecoveryController { InternalRecoveryServiceException { try { return (Map<String, byte[]>) mBinder.recoverKeys( - session.getSessionId(), recoveryKeyBlob, applicationKeys); + session.getSessionId(), + recoveryKeyBlob, + BackwardsCompat.fromLegacyWrappedApplicationKeys(applicationKeys)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { diff --git a/core/java/android/security/keystore/recovery/BadCertificateFormatException.java b/core/java/android/security/keystore/recovery/BadCertificateFormatException.java new file mode 100644 index 000000000000..fda3387bb63b --- /dev/null +++ b/core/java/android/security/keystore/recovery/BadCertificateFormatException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 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.recovery; + +/** + * Error thrown when the recovery agent supplies an invalid X509 certificate. + * + * @hide + */ +public class BadCertificateFormatException extends RecoveryControllerException { + public BadCertificateFormatException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/recovery/DecryptionFailedException.java b/core/java/android/security/keystore/recovery/DecryptionFailedException.java new file mode 100644 index 000000000000..b414dc59834b --- /dev/null +++ b/core/java/android/security/keystore/recovery/DecryptionFailedException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.recovery; + +/** + * Error thrown when decryption failed, due to an agent error. i.e., using the incorrect key, + * trying to decrypt garbage data, trying to decrypt data that has somehow been corrupted, etc. + * + * @hide + */ +public class DecryptionFailedException extends RecoveryControllerException { + + public DecryptionFailedException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java b/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java new file mode 100644 index 000000000000..07a540ceea25 --- /dev/null +++ b/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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.recovery; + +/** + * An error thrown when something went wrong internally in the recovery service. + * + * <p>This is an unexpected error, and indicates a problem with the service itself, rather than the + * caller having performed some kind of illegal action. + * + * @hide + */ +public class InternalRecoveryServiceException extends RecoveryControllerException { + public InternalRecoveryServiceException(String msg) { + super(msg); + } + + public InternalRecoveryServiceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/java/android/security/keystore/KeyDerivationParams.aidl b/core/java/android/security/keystore/recovery/KeyDerivationParams.aidl index f39aa047adee..2b1bbbe35f1a 100644 --- a/core/java/android/security/keystore/KeyDerivationParams.aidl +++ b/core/java/android/security/keystore/recovery/KeyDerivationParams.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security.keystore; +package android.security.keystore.recovery; /* @hide */ parcelable KeyDerivationParams; diff --git a/core/java/android/security/keystore/recovery/KeyDerivationParams.java b/core/java/android/security/keystore/recovery/KeyDerivationParams.java new file mode 100644 index 000000000000..90613952ae9b --- /dev/null +++ b/core/java/android/security/keystore/recovery/KeyDerivationParams.java @@ -0,0 +1,116 @@ +/* + * 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.recovery; + +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. + * Currently only supports salted SHA-256 + * + * @hide + */ +public final class KeyDerivationParams implements Parcelable { + private final int mAlgorithm; + private byte[] mSalt; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ALGORITHM_"}, value = {ALGORITHM_SHA256, ALGORITHM_ARGON2ID}) + public @interface KeyDerivationAlgorithm { + } + + /** + * Salted SHA256 + */ + public static final int ALGORITHM_SHA256 = 1; + + /** + * Argon2ID + * @hide + */ + // 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 KeyDerivationParams createSha256Params(@NonNull byte[] salt) { + return new KeyDerivationParams(ALGORITHM_SHA256, salt); + } + + // TODO: Make private once legacy API is removed + public KeyDerivationParams(@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 Creator<KeyDerivationParams> CREATOR = + new Creator<KeyDerivationParams>() { + public KeyDerivationParams createFromParcel(Parcel in) { + return new KeyDerivationParams(in); + } + + public KeyDerivationParams[] newArray(int length) { + return new KeyDerivationParams[length]; + } + }; + + /** + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mAlgorithm); + out.writeByteArray(mSalt); + } + + /** + * @hide + */ + protected KeyDerivationParams(Parcel in) { + mAlgorithm = in.readInt(); + mSalt = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keystore/KeychainProtectionParams.aidl b/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl index 0341223b081a..538573847b00 100644 --- a/core/java/android/security/keystore/KeychainProtectionParams.aidl +++ b/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security.keystore; +package android.security.keystore.recovery; /* @hide */ parcelable KeychainProtectionParams; diff --git a/core/java/android/security/keystore/recovery/KeychainProtectionParams.java b/core/java/android/security/keystore/recovery/KeychainProtectionParams.java new file mode 100644 index 000000000000..445815b48e7c --- /dev/null +++ b/core/java/android/security/keystore/recovery/KeychainProtectionParams.java @@ -0,0 +1,289 @@ +/* + * 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.recovery; + +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; + +/** + * A {@link KeychainSnapshot} is protected with a key derived from the user's lock screen. This + * class wraps all the data necessary to derive the same key on a recovering device: + * + * <ul> + * <li>UI parameters for the user's lock screen - so that if e.g., the user was using a pattern, + * the recovering device can display the pattern UI to the user when asking them to enter + * the lock screen from their previous device. + * <li>The algorithm used to derive a key from the user's lock screen, e.g. SHA-256 with a salt. + * </ul> + * + * <p>As such, this data is sent along with the {@link KeychainSnapshot} when syncing the current + * version of the keychain. + * + * <p>For now, the recoverable keychain only supports a single layer of protection, which is the + * user's lock screen. In the future, the keychain will support multiple layers of protection + * (e.g. an additional keychain password, along with the lock screen). + * + * @hide + */ +public final class KeychainProtectionParams 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 = 100; + + /** + * Custom passphrase, unrelated to lock screen, is required to recover KeyStore. + */ + public static final int TYPE_CUSTOM_PASSWORD = 101; + + /** @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 Integer mUserSecretType; + + @LockScreenUiFormat + private Integer mLockScreenUiFormat; + + /** + * Parameters of the key derivation function, including algorithm, difficulty, salt. + */ + private KeyDerivationParams mKeyDerivationParams; + 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. + * @hide + */ + public KeychainProtectionParams(@UserSecretType int userSecretType, + @LockScreenUiFormat int lockScreenUiFormat, + @NonNull KeyDerivationParams keyDerivationParams, + @NonNull byte[] secret) { + mUserSecretType = userSecretType; + mLockScreenUiFormat = lockScreenUiFormat; + mKeyDerivationParams = Preconditions.checkNotNull(keyDerivationParams); + mSecret = Preconditions.checkNotNull(secret); + } + + private KeychainProtectionParams() { + + } + + /** + * @see TYPE_LOCKSCREEN + * @see TYPE_CUSTOM_PASSWORD + */ + public @UserSecretType int getUserSecretType() { + return mUserSecretType; + } + + /** + * Specifies UX shown to user during recovery. + * Default value is {@code TYPE_LOCKSCREEN} + * + * @see TYPE_PIN + * @see TYPE_PASSWORD + * @see 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. + */ + @NonNull public KeyDerivationParams getKeyDerivationParams() { + return mKeyDerivationParams; + } + + /** + * Secret derived from user input. + * Default value is empty array + * + * @return secret or empty array + */ + public @NonNull byte[] getSecret() { + return mSecret; + } + + /** + * Builder for creating {@link KeychainProtectionParams}. + */ + public static class Builder { + private KeychainProtectionParams + mInstance = new KeychainProtectionParams(); + + /** + * Sets user secret type. + * + * @see TYPE_LOCKSCREEN + * @see TYPE_CUSTOM_PASSWORD + * @param userSecretType The secret type + * @return This builder. + */ + public Builder setUserSecretType(@UserSecretType int userSecretType) { + mInstance.mUserSecretType = userSecretType; + return this; + } + + /** + * Sets UI format. + * + * @see TYPE_PIN + * @see TYPE_PASSWORD + * @see TYPE_PATTERN + * @param lockScreenUiFormat The UI format + * @return This builder. + */ + public Builder setLockScreenUiFormat(@LockScreenUiFormat int lockScreenUiFormat) { + mInstance.mLockScreenUiFormat = lockScreenUiFormat; + return this; + } + + /** + * Sets parameters of the key derivation function. + * + * @param keyDerivationParams Key derivation Params + * @return This builder. + */ + public Builder setKeyDerivationParams(@NonNull KeyDerivationParams + keyDerivationParams) { + mInstance.mKeyDerivationParams = keyDerivationParams; + return this; + } + + /** + * Secret derived from user input, or empty array. + * + * @param secret The secret. + * @return This builder. + */ + public Builder setSecret(@NonNull byte[] secret) { + mInstance.mSecret = secret; + return this; + } + + + /** + * Creates a new {@link KeychainProtectionParams} instance. + * The instance will include default values, if {@link setSecret} + * or {@link setUserSecretType} were not called. + * + * @return new instance + * @throws NullPointerException if some required fields were not set. + */ + @NonNull public KeychainProtectionParams build() { + if (mInstance.mUserSecretType == null) { + mInstance.mUserSecretType = TYPE_LOCKSCREEN; + } + Preconditions.checkNotNull(mInstance.mLockScreenUiFormat); + Preconditions.checkNotNull(mInstance.mKeyDerivationParams); + if (mInstance.mSecret == null) { + mInstance.mSecret = new byte[]{}; + } + return mInstance; + } + } + + /** + * 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 Creator<KeychainProtectionParams> CREATOR = + new Creator<KeychainProtectionParams>() { + public KeychainProtectionParams createFromParcel(Parcel in) { + return new KeychainProtectionParams(in); + } + + public KeychainProtectionParams[] newArray(int length) { + return new KeychainProtectionParams[length]; + } + }; + + /** + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mUserSecretType); + out.writeInt(mLockScreenUiFormat); + out.writeTypedObject(mKeyDerivationParams, flags); + out.writeByteArray(mSecret); + } + + /** + * @hide + */ + protected KeychainProtectionParams(Parcel in) { + mUserSecretType = in.readInt(); + mLockScreenUiFormat = in.readInt(); + mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR); + mSecret = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keystore/KeychainSnapshot.aidl b/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl index b35713f329d6..7822f39bf269 100644 --- a/core/java/android/security/keystore/KeychainSnapshot.aidl +++ b/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security.keystore; +package android.security.keystore.recovery; /* @hide */ parcelable KeychainSnapshot; diff --git a/core/java/android/security/keystore/recovery/KeychainSnapshot.java b/core/java/android/security/keystore/recovery/KeychainSnapshot.java new file mode 100644 index 000000000000..a8e2725c0339 --- /dev/null +++ b/core/java/android/security/keystore/recovery/KeychainSnapshot.java @@ -0,0 +1,300 @@ +/* + * 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.recovery; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.List; + +/** + * A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot: + * + * <ul> + * <li>The user's lock screen changes. (A key derived from the user's lock screen is used to + * protected the keychain, which is why this forces a new snapshot.) + * <li>A key is added to or removed from the recoverable keychain. + * </ul> + * + * <p>The snapshot data is also encrypted with the remote trusted hardware's public key, so even + * the recovery agent itself should not be able to decipher the data. The recovery agent sends an + * instance of this to the remote trusted hardware whenever a new snapshot is generated. During a + * recovery flow, the recovery agent retrieves a snapshot from the remote trusted hardware. It then + * sends it to the framework, where it is decrypted using the user's lock screen from their previous + * device. + * + * @hide + */ +public final class KeychainSnapshot implements Parcelable { + private static final int DEFAULT_MAX_ATTEMPTS = 10; + private static final long DEFAULT_COUNTER_ID = 1L; + + private int mSnapshotVersion; + private int mMaxAttempts = DEFAULT_MAX_ATTEMPTS; + private long mCounterId = DEFAULT_COUNTER_ID; + private byte[] mServerParams; + private byte[] mPublicKey; + private List<KeychainProtectionParams> mKeychainProtectionParams; + private List<WrappedApplicationKey> mEntryRecoveryData; + private byte[] mEncryptedRecoveryKeyBlob; + + /** + * @hide + * Deprecated, consider using builder. + */ + public KeychainSnapshot( + int snapshotVersion, + @NonNull List<KeychainProtectionParams> keychainProtectionParams, + @NonNull List<WrappedApplicationKey> wrappedApplicationKeys, + @NonNull byte[] encryptedRecoveryKeyBlob) { + mSnapshotVersion = snapshotVersion; + mKeychainProtectionParams = + Preconditions.checkCollectionElementsNotNull(keychainProtectionParams, + "keychainProtectionParams"); + mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(wrappedApplicationKeys, + "wrappedApplicationKeys"); + mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob); + } + + private KeychainSnapshot() { + + } + + /** + * Snapshot version for given account. It is incremented when user secret or list of application + * keys changes. + */ + public int getSnapshotVersion() { + return mSnapshotVersion; + } + + /** + * Number of user secret guesses allowed during Keychain recovery. + */ + public int getMaxAttempts() { + return mMaxAttempts; + } + + /** + * CounterId which is rotated together with user secret. + */ + public long getCounterId() { + return mCounterId; + } + + /** + * Server parameters. + */ + public @NonNull byte[] getServerParams() { + return mServerParams; + } + + /** + * Public key used to encrypt {@code encryptedRecoveryKeyBlob}. + * + * See implementation for binary key format + */ + // TODO: document key format. + public @NonNull byte[] getTrustedHardwarePublicKey() { + return mPublicKey; + } + + /** + * UI and key derivation parameters. Note that combination of secrets may be used. + */ + public @NonNull List<KeychainProtectionParams> getKeychainProtectionParams() { + return mKeychainProtectionParams; + } + + /** + * List of application keys, with key material encrypted by + * the recovery key ({@link #getEncryptedRecoveryKeyBlob}). + */ + public @NonNull List<WrappedApplicationKey> getWrappedApplicationKeys() { + return mEntryRecoveryData; + } + + /** + * Recovery key blob, encrypted by user secret and recovery service public key. + */ + public @NonNull byte[] getEncryptedRecoveryKeyBlob() { + return mEncryptedRecoveryKeyBlob; + } + + public static final Creator<KeychainSnapshot> CREATOR = + new Creator<KeychainSnapshot>() { + public KeychainSnapshot createFromParcel(Parcel in) { + return new KeychainSnapshot(in); + } + + public KeychainSnapshot[] newArray(int length) { + return new KeychainSnapshot[length]; + } + }; + + /** + * Builder for creating {@link KeychainSnapshot}. + */ + public static class Builder { + private KeychainSnapshot + mInstance = new KeychainSnapshot(); + + /** + * Snapshot version for given account. + * + * @param snapshotVersion The snapshot version + * @return This builder. + */ + public Builder setSnapshotVersion(int snapshotVersion) { + mInstance.mSnapshotVersion = snapshotVersion; + return this; + } + + /** + * Sets the number of user secret guesses allowed during Keychain recovery. + * + * @param maxAttempts The maximum number of guesses. + * @return This builder. + */ + public Builder setMaxAttempts(int maxAttempts) { + mInstance.mMaxAttempts = maxAttempts; + return this; + } + + /** + * Sets counter id. + * + * @param counterId The counter id. + * @return This builder. + */ + public Builder setCounterId(long counterId) { + mInstance.mCounterId = counterId; + return this; + } + + /** + * Sets server parameters. + * + * @param serverParams The server parameters + * @return This builder. + */ + public Builder setServerParams(byte[] serverParams) { + mInstance.mServerParams = serverParams; + return this; + } + + /** + * Sets public key used to encrypt recovery blob. + * + * @param publicKey The public key + * @return This builder. + */ + public Builder setTrustedHardwarePublicKey(byte[] publicKey) { + mInstance.mPublicKey = publicKey; + return this; + } + + /** + * Sets UI and key derivation parameters + * + * @param recoveryMetadata The UI and key derivation parameters + * @return This builder. + */ + public Builder setKeychainProtectionParams( + @NonNull List<KeychainProtectionParams> recoveryMetadata) { + mInstance.mKeychainProtectionParams = recoveryMetadata; + return this; + } + + /** + * List of application keys. + * + * @param entryRecoveryData List of application keys + * @return This builder. + */ + public Builder setWrappedApplicationKeys(List<WrappedApplicationKey> entryRecoveryData) { + mInstance.mEntryRecoveryData = entryRecoveryData; + return this; + } + + /** + * Sets recovery key blob + * + * @param encryptedRecoveryKeyBlob The recovery key blob. + * @return This builder. + */ + public Builder setEncryptedRecoveryKeyBlob(@NonNull byte[] encryptedRecoveryKeyBlob) { + mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob; + return this; + } + + + /** + * Creates a new {@link KeychainSnapshot} instance. + * + * @return new instance + * @throws NullPointerException if some required fields were not set. + */ + @NonNull public KeychainSnapshot build() { + Preconditions.checkCollectionElementsNotNull(mInstance.mKeychainProtectionParams, + "recoveryMetadata"); + Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData, + "entryRecoveryData"); + Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob); + Preconditions.checkNotNull(mInstance.mServerParams); + Preconditions.checkNotNull(mInstance.mPublicKey); + return mInstance; + } + } + + /** + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSnapshotVersion); + out.writeTypedList(mKeychainProtectionParams); + out.writeByteArray(mEncryptedRecoveryKeyBlob); + out.writeTypedList(mEntryRecoveryData); + out.writeInt(mMaxAttempts); + out.writeLong(mCounterId); + out.writeByteArray(mServerParams); + out.writeByteArray(mPublicKey); + } + + /** + * @hide + */ + protected KeychainSnapshot(Parcel in) { + mSnapshotVersion = in.readInt(); + mKeychainProtectionParams = in.createTypedArrayList(KeychainProtectionParams.CREATOR); + mEncryptedRecoveryKeyBlob = in.createByteArray(); + mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR); + mMaxAttempts = in.readInt(); + mCounterId = in.readLong(); + mServerParams = in.createByteArray(); + mPublicKey = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/security/keystore/recovery/LockScreenRequiredException.java b/core/java/android/security/keystore/recovery/LockScreenRequiredException.java new file mode 100644 index 000000000000..ced23685690e --- /dev/null +++ b/core/java/android/security/keystore/recovery/LockScreenRequiredException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.recovery; + +/** + * Error thrown when trying to generate keys for a profile that has no lock screen set. + * + * <p>A lock screen must be set, as the lock screen is used to encrypt the snapshot. + * + * @hide + */ +public class LockScreenRequiredException extends RecoveryControllerException { + public LockScreenRequiredException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/recovery/RecoveryClaim.java b/core/java/android/security/keystore/recovery/RecoveryClaim.java new file mode 100644 index 000000000000..11385d883a77 --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoveryClaim.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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.recovery; + +/** + * An attempt to recover a keychain protected by remote secure hardware. + * + * @hide + */ +public class RecoveryClaim { + + private final RecoverySession mRecoverySession; + private final byte[] mClaimBytes; + + RecoveryClaim(RecoverySession recoverySession, byte[] claimBytes) { + mRecoverySession = recoverySession; + mClaimBytes = claimBytes; + } + + /** + * Returns the session associated with the recovery attempt. This is used to match the symmetric + * key, which remains internal to the framework, for decrypting the claim response. + * + * @return The session data. + */ + public RecoverySession getRecoverySession() { + return mRecoverySession; + } + + /** + * Returns the encrypted claim's bytes. + * + * <p>This should be sent by the recovery agent to the remote secure hardware, which will use + * it to decrypt the keychain, before sending it re-encrypted with the session's symmetric key + * to the device. + */ + public byte[] getClaimBytes() { + return mClaimBytes; + } +} diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java new file mode 100644 index 000000000000..1908ce22bac2 --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -0,0 +1,535 @@ +/* + * 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.recovery; + +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.util.Log; + +import com.android.internal.widget.ILockSettings; + +import java.util.List; +import java.util.Map; + +/** + * An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by + * other Android devices belonging to the user. The exported keychain is protected by the user's + * lock screen. + * + * <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible + * for transporting the keychain to remote trusted hardware. This hardware must prevent brute force + * attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10). + * After that number of incorrect guesses, the trusted hardware no longer allows access to the + * key chain. + * + * <p>For now only the recovery agent itself is able to create keys, so it is expected that the + * recovery agent is itself the system app. + * + * <p>A recovery agent requires the privileged permission + * {@code android.Manifest.permission#RECOVER_KEYSTORE}. + * + * @hide + */ +public class RecoveryController { + private static final String TAG = "RecoveryController"; + + /** 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; + + /** + * Failed because no snapshot is yet pending to be synced for the user. + * + * @hide + */ + public static final int ERROR_NO_SNAPSHOT_PENDING = 21; + + /** + * Failed due to an error internal to the recovery service. This is unexpected and indicates + * either a problem with the logic in the service, or a problem with a dependency of the + * service (such as AndroidKeyStore). + * + * @hide + */ + public static final int ERROR_SERVICE_INTERNAL_ERROR = 22; + + /** + * Failed because the user does not have a lock screen set. + * + * @hide + */ + public static final int ERROR_INSECURE_USER = 23; + + /** + * Error thrown when attempting to use a recovery session that has since been closed. + * + * @hide + */ + public static final int ERROR_SESSION_EXPIRED = 24; + + /** + * Failed because the provided certificate was not a valid X509 certificate. + * + * @hide + */ + public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25; + + /** + * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong, + * the data has become corrupted, the data has been tampered with, etc. + * + * @hide + */ + public static final int ERROR_DECRYPTION_FAILED = 26; + + + private final ILockSettings mBinder; + + private RecoveryController(ILockSettings binder) { + mBinder = binder; + } + + /** + * Gets a new instance of the class. + */ + public static RecoveryController getInstance() { + ILockSettings lockSettings = + ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")); + return new RecoveryController(lockSettings); + } + + /** + * Initializes key recovery service for the calling application. RecoveryController + * 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 RecoveryController to select + * which of a set of remote recovery service devices will be used. + * + * <p>In addition, RecoveryController 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 BadCertificateFormatException if the {@code signedPublicKeyList} is in a bad format. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void initRecoveryService( + @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) + throws BadCertificateFormatException, InternalRecoveryServiceException { + try { + mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) { + throw new BadCertificateFormatException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(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. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @NonNull public KeychainSnapshot getRecoveryData(@NonNull byte[] account) + throws InternalRecoveryServiceException { + try { + return mBinder.getRecoveryData(account); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) { + return null; + } + throw wrapUnexpectedServiceSpecificException(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}. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) + throws InternalRecoveryServiceException { + try { + mBinder.setSnapshotCreatedPendingIntent(intent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(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 KeychainSnapshot#getSnapshotVersion + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions() + throws InternalRecoveryServiceException { + try { + // IPC doesn't support generic Maps. + @SuppressWarnings("unchecked") + Map<byte[], Integer> result = + (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions(); + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Server parameters used to generate new recovery key blobs. This value will be included in + * {@code KeychainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included + * in vaultParams {@link #startRecoverySession} + * + * @param serverParams included in recovery key blob. + * @see #getRecoveryData + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException { + try { + mBinder.setServerParams(serverParams); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(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. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void setRecoveryStatus( + @NonNull String packageName, @Nullable String[] aliases, int status) + throws NameNotFoundException, InternalRecoveryServiceException { + try { + mBinder.setRecoveryStatus(packageName, aliases, status); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(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> + * + * @return {@code Map} from KeyStore alias to recovery status. + * @see #setRecoveryStatus + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public Map<String, Integer> getRecoveryStatus() throws InternalRecoveryServiceException { + try { + // IPC doesn't support generic Maps. + @SuppressWarnings("unchecked") + Map<String, Integer> result = + (Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null); + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(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 KeychainProtectionParams#TYPE_LOCKSCREEN} or {@link + * KeychainProtectionParams#TYPE_CUSTOM_PASSWORD} + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void setRecoverySecretTypes( + @NonNull @KeychainProtectionParams.UserSecretType int[] secretTypes) + throws InternalRecoveryServiceException { + try { + mBinder.setRecoverySecretTypes(secretTypes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is + * necessary to generate KeychainSnapshot. + * + * @return list of recovery secret types + * @see KeychainSnapshot + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public @NonNull @KeychainProtectionParams.UserSecretType int[] getRecoverySecretTypes() + throws InternalRecoveryServiceException { + try { + return mBinder.getRecoverySecretTypes(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(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 + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @NonNull + public @KeychainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes() + throws InternalRecoveryServiceException { + try { + return mBinder.getPendingRecoverySecretTypes(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(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 KeychainProtectionParams#clearSecret} to override the secret value in + * memory. + * + * @param recoverySecret user generated secret together with parameters necessary to regenerate + * it on a new device. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void recoverySecretAvailable(@NonNull KeychainProtectionParams recoverySecret) + throws InternalRecoveryServiceException { + try { + mBinder.recoverySecretAvailable(recoverySecret); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(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 verifierPublicKey Encoded {@code java.security.cert.X509Certificate} 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 The recovery claim. Claim provides a b 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. + * @throws BadCertificateFormatException if the {@code verifierPublicKey} is in an incorrect + * format. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + @NonNull public RecoveryClaim startRecoverySession( + @NonNull byte[] verifierPublicKey, + @NonNull byte[] vaultParams, + @NonNull byte[] vaultChallenge, + @NonNull List<KeychainProtectionParams> secrets) + throws BadCertificateFormatException, InternalRecoveryServiceException { + try { + RecoverySession recoverySession = RecoverySession.newInstance(this); + byte[] recoveryClaim = + mBinder.startRecoverySession( + recoverySession.getSessionId(), + verifierPublicKey, + vaultParams, + vaultChallenge, + secrets); + return new RecoveryClaim(recoverySession, recoveryClaim); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) { + throw new BadCertificateFormatException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Imports keys. + * + * @param session Related recovery session, as originally created by invoking + * {@link #startRecoverySession(byte[], byte[], byte[], List)}. + * @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 + * WrappedApplicationKey}. Caller is responsibility to perform certificates check. + * @return Map from alias to raw key material. + * @throws SessionExpiredException if {@code session} has since been closed. + * @throws DecryptionFailedException if unable to decrypt the snapshot. + * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service. + */ + public Map<String, byte[]> recoverKeys( + @NonNull RecoverySession session, + @NonNull byte[] recoveryKeyBlob, + @NonNull List<WrappedApplicationKey> applicationKeys) + throws SessionExpiredException, DecryptionFailedException, + InternalRecoveryServiceException { + try { + return (Map<String, byte[]>) mBinder.recoverKeys( + session.getSessionId(), recoveryKeyBlob, applicationKeys); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_DECRYPTION_FAILED) { + throw new DecryptionFailedException(e.getMessage()); + } + if (e.errorCode == ERROR_SESSION_EXPIRED) { + throw new SessionExpiredException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Deletes all data associated with {@code session}. Should not be invoked directly but via + * {@link RecoverySession#close()}. + * + * @hide + */ + void closeSession(RecoverySession session) { + try { + mBinder.closeSession(session.getSessionId()); + } catch (RemoteException | ServiceSpecificException e) { + Log.e(TAG, "Unexpected error trying to close session", e); + } + } + + /** + * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the + * raw material of the key. + * + * @param alias The key alias. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * @throws LockScreenRequiredException if the user has not set a lock screen. This is required + * to generate recoverable keys, as the snapshots are encrypted using a key derived from the + * lock screen. + */ + public byte[] generateAndStoreKey(@NonNull String alias) + throws InternalRecoveryServiceException, LockScreenRequiredException { + try { + return mBinder.generateAndStoreKey(alias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_INSECURE_USER) { + throw new LockScreenRequiredException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** + * Removes a key called {@code alias} from the recoverable key store. + * + * @param alias The key alias. + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + */ + public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException { + try { + mBinder.removeKey(alias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw wrapUnexpectedServiceSpecificException(e); + } + } + + private InternalRecoveryServiceException wrapUnexpectedServiceSpecificException( + ServiceSpecificException e) { + if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) { + return new InternalRecoveryServiceException(e.getMessage()); + } + + // Should never happen. If it does, it's a bug, and we need to update how the method that + // called this throws its exceptions. + return new InternalRecoveryServiceException("Unexpected error code for method: " + + e.errorCode, e); + } +} diff --git a/core/java/android/security/keystore/recovery/RecoveryControllerException.java b/core/java/android/security/keystore/recovery/RecoveryControllerException.java new file mode 100644 index 000000000000..0fb7c07edd5b --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoveryControllerException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 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.recovery; + +import java.security.GeneralSecurityException; + +/** + * Base exception for errors thrown by {@link RecoveryController}. + * + * @hide + */ +public abstract class RecoveryControllerException extends GeneralSecurityException { + RecoveryControllerException() { } + + RecoveryControllerException(String msg) { + super(msg); + } + + public RecoveryControllerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/java/android/security/keystore/recovery/RecoverySession.java b/core/java/android/security/keystore/recovery/RecoverySession.java new file mode 100644 index 000000000000..89f894529739 --- /dev/null +++ b/core/java/android/security/keystore/recovery/RecoverySession.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 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.recovery; + +import java.security.SecureRandom; + +/** + * Session to recover a {@link KeychainSnapshot} from the remote trusted hardware, initiated by a + * recovery agent. + * + * @hide + */ +public class RecoverySession implements AutoCloseable { + + private static final int SESSION_ID_LENGTH_BYTES = 16; + + private final String mSessionId; + private final RecoveryController mRecoveryController; + + private RecoverySession(RecoveryController recoveryController, String sessionId) { + mRecoveryController = recoveryController; + mSessionId = sessionId; + } + + /** + * A new session, started by {@code recoveryManager}. + */ + static RecoverySession newInstance(RecoveryController recoveryController) { + return new RecoverySession(recoveryController, newSessionId()); + } + + /** + * Returns a new random session ID. + */ + private static String newSessionId() { + SecureRandom secureRandom = new SecureRandom(); + byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES]; + secureRandom.nextBytes(sessionId); + StringBuilder sb = new StringBuilder(); + for (byte b : sessionId) { + sb.append(Byte.toHexString(b, /*upperCase=*/ false)); + } + return sb.toString(); + } + + /** + * An internal session ID, used by the framework to match recovery claims to snapshot responses. + */ + String getSessionId() { + return mSessionId; + } + + @Override + public void close() { + mRecoveryController.closeSession(this); + } +} diff --git a/core/java/android/security/keystore/recovery/SessionExpiredException.java b/core/java/android/security/keystore/recovery/SessionExpiredException.java new file mode 100644 index 000000000000..7fc2b0560d69 --- /dev/null +++ b/core/java/android/security/keystore/recovery/SessionExpiredException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 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.recovery; + +/** + * Error thrown when attempting to use a {@link RecoverySession} that has since expired. + * + * @hide + */ +public class SessionExpiredException extends RecoveryControllerException { + public SessionExpiredException(String msg) { + super(msg); + } +} diff --git a/core/java/android/security/keystore/WrappedApplicationKey.aidl b/core/java/android/security/keystore/recovery/WrappedApplicationKey.aidl index a6294fee03b3..b2d1ae461346 100644 --- a/core/java/android/security/keystore/WrappedApplicationKey.aidl +++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security.keystore; +package android.security.keystore.recovery; /* @hide */ parcelable WrappedApplicationKey; diff --git a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java new file mode 100644 index 000000000000..bca03b389e43 --- /dev/null +++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java @@ -0,0 +1,145 @@ +/* + * 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.recovery; + +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 WrappedApplicationKey implements Parcelable { + private String mAlias; + // The only supported format is AES-256 symmetric key. + private byte[] mEncryptedKeyMaterial; + + /** + * Builder for creating {@link WrappedApplicationKey}. + */ + public static class Builder { + private WrappedApplicationKey + mInstance = new WrappedApplicationKey(); + + /** + * Sets Application-specific alias of the key. + * + * @param alias The alias. + * @return This builder. + */ + public Builder setAlias(@NonNull String alias) { + mInstance.mAlias = alias; + return this; + } + + /** + * Sets key material encrypted by recovery key. + * + * @param encryptedKeyMaterial The key material + * @return This builder + */ + + public Builder setEncryptedKeyMaterial(@NonNull byte[] encryptedKeyMaterial) { + mInstance.mEncryptedKeyMaterial = encryptedKeyMaterial; + return this; + } + + /** + * Creates a new {@link WrappedApplicationKey} instance. + * + * @return new instance + * @throws NullPointerException if some required fields were not set. + */ + @NonNull public WrappedApplicationKey build() { + Preconditions.checkNotNull(mInstance.mAlias); + Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial); + return mInstance; + } + } + + private WrappedApplicationKey() { + + } + + /** + * Deprecated - consider using Builder. + * @hide + */ + public WrappedApplicationKey(@NonNull String 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 String getAlias() { + return mAlias; + } + + /** Key material encrypted by recovery key. */ + public @NonNull byte[] getEncryptedKeyMaterial() { + return mEncryptedKeyMaterial; + } + + public static final Creator<WrappedApplicationKey> CREATOR = + new Creator<WrappedApplicationKey>() { + public WrappedApplicationKey createFromParcel(Parcel in) { + return new WrappedApplicationKey(in); + } + + public WrappedApplicationKey[] newArray(int length) { + return new WrappedApplicationKey[length]; + } + }; + + /** + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mAlias); + out.writeByteArray(mEncryptedKeyMaterial); + } + + /** + * @hide + */ + protected WrappedApplicationKey(Parcel in) { + mAlias = in.readString(); + mEncryptedKeyMaterial = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index e3f1f472ce5e..f9da1eb26669 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -19,9 +19,9 @@ package com.android.internal.widget; import android.app.PendingIntent; import android.app.trust.IStrongAuthTracker; import android.os.Bundle; -import android.security.keystore.WrappedApplicationKey; -import android.security.keystore.KeychainSnapshot; -import android.security.keystore.KeychainProtectionParams; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.security.keystore.recovery.KeychainSnapshot; +import android.security.keystore.recovery.KeychainProtectionParams; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.VerifyCredentialResponse; diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index eb12e1c2d584..a6753ab09103 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -78,10 +78,10 @@ import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; -import android.security.keystore.KeychainProtectionParams; import android.security.keystore.UserNotAuthenticatedException; -import android.security.keystore.WrappedApplicationKey; -import android.security.keystore.KeychainSnapshot; +import android.security.keystore.recovery.KeychainProtectionParams; +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; diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 452c9eea79e8..6fcbcbcbfad9 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -16,14 +16,14 @@ package com.android.server.locksettings.recoverablekeystore; -import static android.security.keystore.KeychainProtectionParams.TYPE_LOCKSCREEN; +import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_LOCKSCREEN; import android.annotation.Nullable; import android.content.Context; -import android.security.keystore.KeyDerivationParams; -import android.security.keystore.KeychainProtectionParams; -import android.security.keystore.KeychainSnapshot; -import android.security.keystore.WrappedApplicationKey; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.KeychainProtectionParams; +import android.security.keystore.recovery.KeychainSnapshot; +import android.security.keystore.recovery.WrappedApplicationKey; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 76508d5817e2..ac3cef2fa7ef 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -33,10 +33,10 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; -import android.security.keystore.KeychainProtectionParams; -import android.security.keystore.KeychainSnapshot; -import android.security.keystore.RecoveryController; -import android.security.keystore.WrappedApplicationKey; +import android.security.keystore.recovery.KeychainProtectionParams; +import android.security.keystore.recovery.KeychainSnapshot; +import android.security.keystore.recovery.RecoveryController; +import android.security.keystore.recovery.WrappedApplicationKey; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java index 62bb41ec48aa..7cde7ab0589c 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java @@ -17,7 +17,7 @@ package com.android.server.locksettings.recoverablekeystore.storage; import android.annotation.Nullable; -import android.security.keystore.KeychainSnapshot; +import android.security.keystore.recovery.KeychainSnapshot; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index 7eec4fea64dc..6edaf87e135f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -16,11 +16,11 @@ package com.android.server.locksettings.recoverablekeystore; -import static android.security.keystore.KeychainProtectionParams.TYPE_LOCKSCREEN; +import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_LOCKSCREEN; -import static android.security.keystore.KeychainProtectionParams.TYPE_PASSWORD; -import static android.security.keystore.KeychainProtectionParams.TYPE_PATTERN; -import static android.security.keystore.KeychainProtectionParams.TYPE_PIN; +import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PASSWORD; +import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PATTERN; +import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; @@ -40,9 +40,9 @@ import android.content.Context; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; -import android.security.keystore.KeyDerivationParams; -import android.security.keystore.KeychainSnapshot; -import android.security.keystore.WrappedApplicationKey; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.KeychainSnapshot; +import android.security.keystore.recovery.WrappedApplicationKey; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 970bc33337da..a3a2e4764edc 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -16,8 +16,8 @@ package com.android.server.locksettings.recoverablekeystore; -import static android.security.keystore.KeychainProtectionParams.TYPE_LOCKSCREEN; -import static android.security.keystore.KeychainProtectionParams.TYPE_PASSWORD; +import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_LOCKSCREEN; +import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PASSWORD; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -42,9 +42,9 @@ import android.os.UserHandle; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; -import android.security.keystore.KeyDerivationParams; -import android.security.keystore.KeychainProtectionParams; -import android.security.keystore.WrappedApplicationKey; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.KeychainProtectionParams; +import android.security.keystore.recovery.WrappedApplicationKey; import android.support.test.filters.SmallTest; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java index 56b44e2fa17c..89c5c6c8a072 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java @@ -3,7 +3,7 @@ package com.android.server.locksettings.recoverablekeystore.storage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import android.security.keystore.KeychainSnapshot; +import android.security.keystore.recovery.KeychainSnapshot; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; |