diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-04-15 00:41:15 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-04-15 00:41:15 -0400 |
commit | b8042fc9b036db0a6692ca853428fc6ab1e60892 (patch) | |
tree | 82669ea5d75238758e22d379a42baeada526219e /android/security | |
parent | 4d01eeaffaa720e4458a118baa137a11614f00f7 (diff) | |
download | android-28-b8042fc9b036db0a6692ca853428fc6ab1e60892.tar.gz |
Import Android SDK Platform P [4719250]HEADmastermainandroidx-work-releaseandroidx-webkit-releaseandroidx-viewpager2-releaseandroidx-versionedparcelable-releaseandroidx-vectordrawable-releaseandroidx-transition-releaseandroidx-sqlite-releaseandroidx-sharetarget-releaseandroidx-security-security-crypto-releaseandroidx-savedstate-releaseandroidx-room-releaseandroidx-recyclerview-releaseandroidx-recyclerview-recyclerview-selection-releaseandroidx-preference-releaseandroidx-paging-releaseandroidx-paging-legacy-releaseandroidx-navigation-releaseandroidx-mediarouter-releaseandroidx-media2-releaseandroidx-media2-media2-widget-releaseandroidx-media-releaseandroidx-master-releaseandroidx-localbroadcastmanager-releaseandroidx-loader-releaseandroidx-lifecycle-releaseandroidx-jetifier-releaseandroidx-g3-releaseandroidx-fragment-releaseandroidx-exifinterface-releaseandroidx-enterprise-releaseandroidx-core-releaseandroidx-core-core-role-releaseandroidx-coordinatorlayout-releaseandroidx-concurrent-releaseandroidx-compose-releaseandroidx-collection-releaseandroidx-camerax-releaseandroidx-browser-releaseandroidx-biometric-releaseandroidx-benchmark-releaseandroidx-autofill-releaseandroidx-arch-core-releaseandroidx-appcompat-releaseandroidx-annotation-releaseandroidx-annotation-annotation-experimental-releaseandroidx-activity-releaseandroid-arch-work-releaseandroid-arch-navigation-release
/google/data/ro/projects/android/fetch_artifact \
--bid 4719250 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4719250.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: I9ec0a12c9251b8449dba0d86b0cfdbcca16b0a7c
Diffstat (limited to 'android/security')
21 files changed, 111 insertions, 1721 deletions
diff --git a/android/security/ConfirmationCallback.java b/android/security/ConfirmationCallback.java index 4670bce3..fd027f0f 100644 --- a/android/security/ConfirmationCallback.java +++ b/android/security/ConfirmationCallback.java @@ -33,22 +33,22 @@ public abstract class ConfirmationCallback { * * @param dataThatWasConfirmed the data that was confirmed, see above for the format. */ - public void onConfirmedByUser(@NonNull byte[] dataThatWasConfirmed) {} + public void onConfirmed(@NonNull byte[] dataThatWasConfirmed) {} /** * Called when the requested prompt was dismissed (not accepted) by the user. */ - public void onDismissedByUser() {} + public void onDismissed() {} /** * Called when the requested prompt was dismissed by the application. */ - public void onDismissedByApplication() {} + public void onCanceled() {} /** * Called when the requested prompt was dismissed because of a low-level error. * - * @param e an exception representing the error. + * @param e a throwable representing the error. */ - public void onError(Exception e) {} + public void onError(Throwable e) {} } diff --git a/android/security/ConfirmationDialog.java b/android/security/ConfirmationPrompt.java index 1697106c..5330cffe 100644 --- a/android/security/ConfirmationDialog.java +++ b/android/security/ConfirmationPrompt.java @@ -68,7 +68,7 @@ import java.util.concurrent.Executor; * {@link #presentPrompt presentPrompt()} method. The <i>Relying Party</i> stores the nonce locally * since it'll use it in a later step. * <li> If the user approves the prompt a <i>Confirmation Response</i> is returned in the - * {@link ConfirmationCallback#onConfirmedByUser onConfirmedByUser(byte[])} callback as the + * {@link ConfirmationCallback#onConfirmed onConfirmed(byte[])} callback as the * <code>dataThatWasConfirmed</code> parameter. This blob contains the text that was shown to the * user, the <code>extraData</code> parameter, and possibly other data. * <li> The application signs the <i>Confirmation Response</i> with the previously created key and @@ -82,8 +82,8 @@ import java.util.concurrent.Executor; * last bullet, is to have the <i>Relying Party</i> generate <code>promptText</code> and store it * along the nonce in the <code>extraData</code> blob. */ -public class ConfirmationDialog { - private static final String TAG = "ConfirmationDialog"; +public class ConfirmationPrompt { + private static final String TAG = "ConfirmationPrompt"; private CharSequence mPromptText; private byte[] mExtraData; @@ -97,15 +97,15 @@ public class ConfirmationDialog { ConfirmationCallback callback) { switch (responseCode) { case KeyStore.CONFIRMATIONUI_OK: - callback.onConfirmedByUser(dataThatWasConfirmed); + callback.onConfirmed(dataThatWasConfirmed); break; case KeyStore.CONFIRMATIONUI_CANCELED: - callback.onDismissedByUser(); + callback.onDismissed(); break; case KeyStore.CONFIRMATIONUI_ABORTED: - callback.onDismissedByApplication(); + callback.onCanceled(); break; case KeyStore.CONFIRMATIONUI_SYSTEM_ERROR: @@ -145,21 +145,25 @@ public class ConfirmationDialog { }; /** - * A builder that collects arguments, to be shown on the system-provided confirmation dialog. + * A builder that collects arguments, to be shown on the system-provided confirmation prompt. */ - public static class Builder { + public static final class Builder { + private Context mContext; private CharSequence mPromptText; private byte[] mExtraData; /** - * Creates a builder for the confirmation dialog. + * Creates a builder for the confirmation prompt. + * + * @param context the application context */ - public Builder() { + public Builder(Context context) { + mContext = context; } /** - * Sets the prompt text for the dialog. + * Sets the prompt text for the prompt. * * @param promptText the text to present in the prompt. * @return the builder. @@ -170,7 +174,7 @@ public class ConfirmationDialog { } /** - * Sets the extra data for the dialog. + * Sets the extra data for the prompt. * * @param extraData data to include in the response data. * @return the builder. @@ -181,24 +185,23 @@ public class ConfirmationDialog { } /** - * Creates a {@link ConfirmationDialog} with the arguments supplied to this builder. + * Creates a {@link ConfirmationPrompt} with the arguments supplied to this builder. * - * @param context the application context - * @return a {@link ConfirmationDialog} + * @return a {@link ConfirmationPrompt} * @throws IllegalArgumentException if any of the required fields are not set. */ - public ConfirmationDialog build(Context context) { + public ConfirmationPrompt build() { if (TextUtils.isEmpty(mPromptText)) { throw new IllegalArgumentException("prompt text must be set and non-empty"); } if (mExtraData == null) { throw new IllegalArgumentException("extraData must be set"); } - return new ConfirmationDialog(context, mPromptText, mExtraData); + return new ConfirmationPrompt(mContext, mPromptText, mExtraData); } } - private ConfirmationDialog(Context context, CharSequence promptText, byte[] extraData) { + private ConfirmationPrompt(Context context, CharSequence promptText, byte[] extraData) { mContext = context; mPromptText = promptText; mExtraData = extraData; @@ -227,10 +230,10 @@ public class ConfirmationDialog { return uiOptionsAsFlags; } - private boolean isAccessibilityServiceRunning() { + private static boolean isAccessibilityServiceRunning(Context context) { boolean serviceRunning = false; try { - ContentResolver contentResolver = mContext.getContentResolver(); + ContentResolver contentResolver = context.getContentResolver(); int a11yEnabled = Settings.Secure.getInt(contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED); if (a11yEnabled == 1) { @@ -249,12 +252,12 @@ public class ConfirmationDialog { * When the prompt is no longer being presented, one of the methods in * {@link ConfirmationCallback} is called on the supplied callback object. * - * Confirmation dialogs may not be available when accessibility services are running so this + * Confirmation prompts may not be available when accessibility services are running so this * may fail with a {@link ConfirmationNotAvailableException} exception even if * {@link #isSupported} returns {@code true}. * * @param executor the executor identifying the thread that will receive the callback. - * @param callback the callback to use when the dialog is done showing. + * @param callback the callback to use when the prompt is done showing. * @throws IllegalArgumentException if the prompt text is too long or malfomed. * @throws ConfirmationAlreadyPresentingException if another prompt is being presented. * @throws ConfirmationNotAvailableException if confirmation prompts are not supported. @@ -265,7 +268,7 @@ public class ConfirmationDialog { if (mCallback != null) { throw new ConfirmationAlreadyPresentingException(); } - if (isAccessibilityServiceRunning()) { + if (isAccessibilityServiceRunning(mContext)) { throw new ConfirmationNotAvailableException(); } mCallback = callback; @@ -301,7 +304,7 @@ public class ConfirmationDialog { * Cancels a prompt currently being displayed. * * On success, the - * {@link ConfirmationCallback#onDismissedByApplication onDismissedByApplication()} method on + * {@link ConfirmationCallback#onCanceled onCanceled()} method on * the supplied callback object will be called asynchronously. * * @throws IllegalStateException if no prompt is currently being presented. @@ -324,9 +327,13 @@ public class ConfirmationDialog { /** * Checks if the device supports confirmation prompts. * + * @param context the application context. * @return true if confirmation prompts are supported by the device. */ - public static boolean isSupported() { + public static boolean isSupported(Context context) { + if (isAccessibilityServiceRunning(context)) { + return false; + } return KeyStore.getInstance().isConfirmationPromptSupported(); } } diff --git a/android/security/KeyStoreException.java b/android/security/KeyStoreException.java index 88e768ce..30389a29 100644 --- a/android/security/KeyStoreException.java +++ b/android/security/KeyStoreException.java @@ -16,12 +16,15 @@ package android.security; +import android.annotation.TestApi; + /** * KeyStore/keymaster exception with positive error codes coming from the KeyStore and negative * ones from keymaster. * * @hide */ +@TestApi public class KeyStoreException extends Exception { private final int mErrorCode; diff --git a/android/security/keystore/BackwardsCompat.java b/android/security/keystore/BackwardsCompat.java deleted file mode 100644 index cf5fe1f0..00000000 --- a/android/security/keystore/BackwardsCompat.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 android.security.keystore.recovery.KeyDerivationParams.createSha256Params( - 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/android/security/keystore/BadCertificateFormatException.java b/android/security/keystore/BadCertificateFormatException.java deleted file mode 100644 index c51b7737..00000000 --- a/android/security/keystore/BadCertificateFormatException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.BadCertificateFormatException}. - * @hide - */ -public class BadCertificateFormatException extends RecoveryControllerException { - public BadCertificateFormatException(String msg) { - super(msg); - } -} diff --git a/android/security/keystore/DecryptionFailedException.java b/android/security/keystore/DecryptionFailedException.java deleted file mode 100644 index c0b52f71..00000000 --- a/android/security/keystore/DecryptionFailedException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.DecryptionFailedException}. - * @hide - */ -public class DecryptionFailedException extends RecoveryControllerException { - - public DecryptionFailedException(String msg) { - super(msg); - } -} diff --git a/android/security/keystore/InternalRecoveryServiceException.java b/android/security/keystore/InternalRecoveryServiceException.java deleted file mode 100644 index 40076f73..00000000 --- a/android/security/keystore/InternalRecoveryServiceException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.InternalRecoveryServiceException}. - * @hide - */ -public class InternalRecoveryServiceException extends RecoveryControllerException { - public InternalRecoveryServiceException(String msg) { - super(msg); - } - - public InternalRecoveryServiceException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/android/security/keystore/KeyDerivationParams.java b/android/security/keystore/KeyDerivationParams.java deleted file mode 100644 index e475dc36..00000000 --- a/android/security/keystore/KeyDerivationParams.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore; - -import android.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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.KeyDerivationParams}. - * @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); - } - - 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 Parcelable.Creator<KeyDerivationParams> CREATOR = - new Parcelable.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/android/security/keystore/KeyGenParameterSpec.java b/android/security/keystore/KeyGenParameterSpec.java index c0d0fb00..b2e0f675 100644 --- a/android/security/keystore/KeyGenParameterSpec.java +++ b/android/security/keystore/KeyGenParameterSpec.java @@ -19,6 +19,7 @@ package android.security.keystore; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.KeyguardManager; import android.hardware.fingerprint.FingerprintManager; import android.security.GateKeeper; @@ -594,6 +595,14 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu /** * Returns {@code true} if the key is authorized to be used only if a test of user presence has * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls. + * It requires that the KeyStore implementation have a direct way to validate the user presence + * for example a KeyStore hardware backed strongbox can use a button press that is observable + * in hardware. A test for user presence is tangential to authentication. The test can be part + * of an authentication step as long as this step can be validated by the hardware protecting + * the key and cannot be spoofed. For example, a physical button press can be used as a test of + * user presence if the other pins connected to the button are not able to simulate a button + * press. There must be no way for the primary processor to fake a button press, or that + * button must not be used as a test of user presence. */ public boolean isUserPresenceRequired() { return mUserPresenceRequired; @@ -673,8 +682,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Returns {@code true} if the screen must be unlocked for this key to be used for encryption or - * signing. Decryption and signature verification will still be available when the screen is + * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or + * signing. Encryption and signature verification will still be available when the screen is * locked. * * @see Builder#setUnlockedDeviceRequired(boolean) @@ -1180,6 +1189,14 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu /** * Sets whether a test of user presence is required to be performed between the * {@code Signature.initSign()} and {@code Signature.sign()} method calls. + * It requires that the KeyStore implementation have a direct way to validate the user + * presence for example a KeyStore hardware backed strongbox can use a button press that + * is observable in hardware. A test for user presence is tangential to authentication. The + * test can be part of an authentication step as long as this step can be validated by the + * hardware protecting the key and cannot be spoofed. For example, a physical button press + * can be used as a test of user presence if the other pins connected to the button are not + * able to simulate a button press.There must be no way for the primary processor to fake a + * button press, or that button must not be used as a test of user presence. */ @NonNull public Builder setUserPresenceRequired(boolean required) { @@ -1227,6 +1244,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * * Sets whether to include a temporary unique ID field in the attestation certificate. */ + @TestApi @NonNull public Builder setUniqueIdIncluded(boolean uniqueIdIncluded) { mUniqueIdIncluded = uniqueIdIncluded; diff --git a/android/security/keystore/KeyProtection.java b/android/security/keystore/KeyProtection.java index 4daf30ce..fdcad85b 100644 --- a/android/security/keystore/KeyProtection.java +++ b/android/security/keystore/KeyProtection.java @@ -19,6 +19,7 @@ package android.security.keystore; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.KeyguardManager; import android.hardware.fingerprint.FingerprintManager; import android.security.GateKeeper; @@ -445,6 +446,14 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { /** * Returns {@code true} if the key is authorized to be used only if a test of user presence has * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls. + * It requires that the KeyStore implementation have a direct way to validate the user presence + * for example a KeyStore hardware backed strongbox can use a button press that is observable + * in hardware. A test for user presence is tangential to authentication. The test can be part + * of an authentication step as long as this step can be validated by the hardware protecting + * the key and cannot be spoofed. For example, a physical button press can be used as a test of + * user presence if the other pins connected to the button are not able to simulate a button + * press. There must be no way for the primary processor to fake a button press, or that + * button must not be used as a test of user presence. */ public boolean isUserPresenceRequired() { return mUserPresenceRequred; @@ -493,6 +502,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see KeymasterUtils#addUserAuthArgs * @hide */ + @TestApi public long getBoundToSpecificSecureUserId() { return mBoundToSecureUserId; } @@ -508,8 +518,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { } /** - * Returns {@code true} if the screen must be unlocked for this key to be used for encryption or - * signing. Decryption and signature verification will still be available when the screen is + * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or + * signing. Encryption and signature verification will still be available when the screen is * locked. * * @see Builder#setUnlockedDeviceRequired(boolean) @@ -840,7 +850,15 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { /** * Sets whether a test of user presence is required to be performed between the - * {@code Signature.initSign()} and {@code Signature.sign()} method calls. + * {@code Signature.initSign()} and {@code Signature.sign()} method calls. It requires that + * the KeyStore implementation have a direct way to validate the user presence for example + * a KeyStore hardware backed strongbox can use a button press that is observable in + * hardware. A test for user presence is tangential to authentication. The test can be part + * of an authentication step as long as this step can be validated by the hardware + * protecting the key and cannot be spoofed. For example, a physical button press can be + * used as a test of user presence if the other pins connected to the button are not able + * to simulate a button press. There must be no way for the primary processor to fake a + * button press, or that button must not be used as a test of user presence. */ @NonNull public Builder setUserPresenceRequired(boolean required) { @@ -910,6 +928,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see KeyProtection#getBoundToSpecificSecureUserId() * @hide */ + @TestApi public Builder setBoundToSpecificSecureUserId(long secureUserId) { mBoundToSecureUserId = secureUserId; return this; diff --git a/android/security/keystore/KeychainProtectionParams.java b/android/security/keystore/KeychainProtectionParams.java deleted file mode 100644 index 19a087d5..00000000 --- a/android/security/keystore/KeychainProtectionParams.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore; - -import android.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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.KeyChainProtectionParams}. - * @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. - */ - public @NonNull 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 Parcelable.Creator<KeychainProtectionParams> CREATOR = - new Parcelable.Creator<KeychainProtectionParams>() { - public KeychainProtectionParams createFromParcel(Parcel in) { - return new KeychainProtectionParams(in); - } - - public KeychainProtectionParams[] newArray(int length) { - return new KeychainProtectionParams[length]; - } - }; - - @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/android/security/keystore/KeychainSnapshot.java b/android/security/keystore/KeychainSnapshot.java deleted file mode 100644 index cf18fd1c..00000000 --- a/android/security/keystore/KeychainSnapshot.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore; - -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.internal.util.Preconditions; - -import java.util.List; - -/** - * @deprecated Use {@link android.security.keystore.recovery.KeyChainSnapshot}. - * @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 Parcelable.Creator<KeychainSnapshot> CREATOR = - new Parcelable.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}. - * - * @hide - */ - 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; - } - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mSnapshotVersion); - out.writeTypedList(mKeychainProtectionParams); - out.writeByteArray(mEncryptedRecoveryKeyBlob); - out.writeTypedList(mEntryRecoveryData); - } - - /** - * @hide - */ - protected KeychainSnapshot(Parcel in) { - mSnapshotVersion = in.readInt(); - mKeychainProtectionParams = in.createTypedArrayList(KeychainProtectionParams.CREATOR); - mEncryptedRecoveryKeyBlob = in.createByteArray(); - mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/android/security/keystore/LockScreenRequiredException.java b/android/security/keystore/LockScreenRequiredException.java deleted file mode 100644 index 09702845..00000000 --- a/android/security/keystore/LockScreenRequiredException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.LockScreenRequiredException}. - * @hide - */ -public class LockScreenRequiredException extends RecoveryControllerException { - public LockScreenRequiredException(String msg) { - super(msg); - } -} diff --git a/android/security/keystore/RecoveryClaim.java b/android/security/keystore/RecoveryClaim.java deleted file mode 100644 index 12be607a..00000000 --- a/android/security/keystore/RecoveryClaim.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.RecoverySession}. - * @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/android/security/keystore/RecoveryController.java b/android/security/keystore/RecoveryController.java deleted file mode 100644 index ca67e35b..00000000 --- a/android/security/keystore/RecoveryController.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.PendingIntent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.RemoteException; -import android.os.ServiceSpecificException; -import android.util.Log; - -import com.android.internal.widget.ILockSettings; - -import java.util.List; -import java.util.Map; - -/** - * @deprecated Use {@link android.security.keystore.recovery.RecoveryController}. - * @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; - } - - /** - * Deprecated. - * Gets a new instance of the class. - */ - public static RecoveryController getInstance() { - throw new UnsupportedOperationException("using Deprecated RecoveryController version"); - } - - /** - * 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 { - throw new UnsupportedOperationException("Deprecated initRecoveryService method called"); - - } - - /** - * 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. - */ - public @NonNull KeychainSnapshot getRecoveryData(@NonNull byte[] account) - throws InternalRecoveryServiceException { - try { - return BackwardsCompat.toLegacyKeychainSnapshot(mBinder.getKeyChainSnapshot()); - } 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 { - throw new UnsupportedOperationException(); - } - - /** - * 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 { - for (String alias : aliases) { - mBinder.setRecoveryStatus(alias, 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(); - 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 { - throw new UnsupportedOperationException(); - } - - /** - * 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, - BackwardsCompat.fromLegacyKeychainProtectionParams(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, - BackwardsCompat.fromLegacyWrappedApplicationKeys(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 { - throw new UnsupportedOperationException(); - } - - /** - * 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/android/security/keystore/RecoveryControllerException.java b/android/security/keystore/RecoveryControllerException.java deleted file mode 100644 index f990c236..00000000 --- a/android/security/keystore/RecoveryControllerException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.security.GeneralSecurityException; - -/** - * @deprecated Use {@link android.security.keystore.recovery.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/android/security/keystore/RecoverySession.java b/android/security/keystore/RecoverySession.java deleted file mode 100644 index 8a3e06b7..00000000 --- a/android/security/keystore/RecoverySession.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.security.SecureRandom; - -/** - * @deprecated Use {@link android.security.keystore.recovery.RecoverySession}. - * @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/android/security/keystore/SessionExpiredException.java b/android/security/keystore/SessionExpiredException.java deleted file mode 100644 index 7c8d5e4f..00000000 --- a/android/security/keystore/SessionExpiredException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -/** - * @deprecated Use {@link android.security.keystore.recovery.SessionExpiredException}. - * @hide - */ -public class SessionExpiredException extends RecoveryControllerException { - public SessionExpiredException(String msg) { - super(msg); - } -} diff --git a/android/security/keystore/UserPresenceUnavailableException.java b/android/security/keystore/UserPresenceUnavailableException.java index cf4099ef..1b053a5c 100644 --- a/android/security/keystore/UserPresenceUnavailableException.java +++ b/android/security/keystore/UserPresenceUnavailableException.java @@ -16,13 +16,13 @@ package android.security.keystore; -import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; /** * Indicates the condition that a proof of user-presence was * requested but this proof was not presented. */ -public class UserPresenceUnavailableException extends InvalidAlgorithmParameterException { +public class UserPresenceUnavailableException extends InvalidKeyException { /** * Constructs a {@code UserPresenceUnavailableException} without a detail message or cause. */ diff --git a/android/security/keystore/WrappedApplicationKey.java b/android/security/keystore/WrappedApplicationKey.java deleted file mode 100644 index 2ce8c7d3..00000000 --- a/android/security/keystore/WrappedApplicationKey.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keystore; - -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.internal.util.Preconditions; - -/** - * @deprecated Use {@link android.security.keystore.recovery.WrappedApplicationKey}. - * @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 Parcelable.Creator<WrappedApplicationKey> CREATOR = - new Parcelable.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/android/security/keystore/recovery/RecoveryController.java b/android/security/keystore/recovery/RecoveryController.java index 281822a3..b84843bf 100644 --- a/android/security/keystore/recovery/RecoveryController.java +++ b/android/security/keystore/recovery/RecoveryController.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.KeyguardManager; import android.app.PendingIntent; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; @@ -250,6 +251,16 @@ public class RecoveryController { */ public static final int ERROR_INVALID_CERTIFICATE = 28; + + /** + * Failed because the provided certificate contained serial version which is lower that the + * version device is already initialized with. It is not possible to downgrade serial version of + * the provided certificate. + * + * @hide + */ + public static final int ERROR_DOWNGRADE_CERTIFICATE = 29; + private final ILockSettings mBinder; private final KeyStore mKeyStore; @@ -278,6 +289,18 @@ public class RecoveryController { } /** + * Checks whether the recoverable key store is currently available. + * + * <p>If it returns true, the device must currently be using a screen lock that is supported for + * use with the recoverable key store, i.e. AOSP PIN, pattern or password. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public static boolean isRecoverableKeyStoreEnabled(@NonNull Context context) { + KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); + return keyguardManager != null && keyguardManager.isDeviceSecure(); + } + + /** * @deprecated Use {@link #initRecoveryService(String, byte[], byte[])} instead. */ @Deprecated @@ -340,6 +363,10 @@ public class RecoveryController { || e.errorCode == ERROR_INVALID_CERTIFICATE) { throw new CertificateException("Invalid certificate for recovery service", e); } + if (e.errorCode == ERROR_DOWNGRADE_CERTIFICATE) { + throw new CertificateException( + "Downgrading certificate serial version isn't supported.", e); + } throw wrapUnexpectedServiceSpecificException(e); } } |