diff --git a/api/current.txt b/api/current.txt index 44f2f08360495..90817e25d035a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -38198,6 +38198,37 @@ package android.security { method public java.security.KeyPair getKeyPair(); } + public class ConfirmationAlreadyPresentingException extends java.lang.Exception { + ctor public ConfirmationAlreadyPresentingException(); + ctor public ConfirmationAlreadyPresentingException(java.lang.String); + } + + public abstract class ConfirmationCallback { + ctor public ConfirmationCallback(); + method public void onConfirmedByUser(byte[]); + method public void onDismissedByApplication(); + method public void onDismissedByUser(); + method public void onError(java.lang.Exception); + } + + public class ConfirmationDialog { + method public void cancelPrompt(); + method public static boolean isSupported(); + method public void presentPrompt(java.util.concurrent.Executor, android.security.ConfirmationCallback) throws android.security.ConfirmationAlreadyPresentingException, android.security.ConfirmationNotAvailableException; + } + + public static class ConfirmationDialog.Builder { + ctor public ConfirmationDialog.Builder(); + method public android.security.ConfirmationDialog build(android.content.Context); + method public android.security.ConfirmationDialog.Builder setExtraData(byte[]); + method public android.security.ConfirmationDialog.Builder setPromptText(java.lang.CharSequence); + } + + public class ConfirmationNotAvailableException extends java.lang.Exception { + ctor public ConfirmationNotAvailableException(); + ctor public ConfirmationNotAvailableException(java.lang.String); + } + public final class KeyChain { ctor public KeyChain(); method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String); @@ -38307,6 +38338,7 @@ package android.security.keystore { method public boolean isTrustedUserPresenceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); + method public boolean isUserConfirmationRequired(); } public static final class KeyGenParameterSpec.Builder { @@ -38334,6 +38366,7 @@ package android.security.keystore { method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int); + method public android.security.keystore.KeyGenParameterSpec.Builder setUserConfirmationRequired(boolean); } public class KeyInfo implements java.security.spec.KeySpec { @@ -38355,6 +38388,7 @@ package android.security.keystore { method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware(); method public boolean isUserAuthenticationValidWhileOnBody(); + method public boolean isUserConfirmationRequired(); } public class KeyNotYetValidException extends java.security.InvalidKeyException { @@ -38422,6 +38456,7 @@ package android.security.keystore { method public boolean isRandomizedEncryptionRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); + method public boolean isUserConfirmationRequired(); } public static final class KeyProtection.Builder { @@ -38440,6 +38475,7 @@ package android.security.keystore { method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int); + method public android.security.keystore.KeyProtection.Builder setUserConfirmationRequired(boolean); } public class StrongBoxUnavailableException extends java.security.ProviderException { diff --git a/core/java/android/security/ConfirmationAlreadyPresentingException.java b/core/java/android/security/ConfirmationAlreadyPresentingException.java new file mode 100644 index 0000000000000..ae4ec5a1bf45d --- /dev/null +++ b/core/java/android/security/ConfirmationAlreadyPresentingException.java @@ -0,0 +1,29 @@ +/* + * Copyright 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; + +/** + * This exception is thrown when presenting a prompt fails because another prompt is already + * being presented. + */ +public class ConfirmationAlreadyPresentingException extends Exception { + public ConfirmationAlreadyPresentingException() {} + + public ConfirmationAlreadyPresentingException(String message) { + super(message); + } +} diff --git a/core/java/android/security/ConfirmationCallback.java b/core/java/android/security/ConfirmationCallback.java new file mode 100644 index 0000000000000..4670bce3a9e0d --- /dev/null +++ b/core/java/android/security/ConfirmationCallback.java @@ -0,0 +1,54 @@ +/* + * Copyright 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; + +import android.annotation.NonNull; + +/** + * Callback class used when signaling that a prompt is no longer being presented. + */ +public abstract class ConfirmationCallback { + /** + * Called when the requested prompt was accepted by the user. + * + * The format of 'dataThatWasConfirmed' parameter is a CBOR + * encoded map (type 5) with (at least) the keys prompt and + * extra. The keys are encoded as CBOR text string (type 3). The value of + * promptText is encoded as CBOR text string (type 3), and the value of extraData is encoded as + * CBOR byte string (type 2). Other keys may be added in the future. + * + * @param dataThatWasConfirmed the data that was confirmed, see above for the format. + */ + public void onConfirmedByUser(@NonNull byte[] dataThatWasConfirmed) {} + + /** + * Called when the requested prompt was dismissed (not accepted) by the user. + */ + public void onDismissedByUser() {} + + /** + * Called when the requested prompt was dismissed by the application. + */ + public void onDismissedByApplication() {} + + /** + * Called when the requested prompt was dismissed because of a low-level error. + * + * @param e an exception representing the error. + */ + public void onError(Exception e) {} +} diff --git a/core/java/android/security/ConfirmationDialog.java b/core/java/android/security/ConfirmationDialog.java new file mode 100644 index 0000000000000..e9df3705db6ef --- /dev/null +++ b/core/java/android/security/ConfirmationDialog.java @@ -0,0 +1,283 @@ +/* + * Copyright 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; + +import android.annotation.NonNull; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Locale; +import java.util.concurrent.Executor; + +/** + * Class used for displaying confirmation prompts. + * + *

Confirmation prompts are prompts shown to the user to confirm a given text and are + * implemented in a way that a positive response indicates with high confidence that the user has + * seen the given text, even if the Android framework (including the kernel) was + * compromised. Implementing confirmation prompts with these guarantees requires dedicated + * hardware-support and may not always be available. + * + *

Confirmation prompts are typically used with an external entitity - the Relying Party - + * in the following way. The setup steps are as follows: + *

+ * + *

The Relying Party is typically an external device (for example connected via + * Bluetooth) or application server. + * + *

Before executing a transaction which requires a high assurance of user content, the + * application does the following: + *

+ * + *

A common way of implementing the "promptText is what is expected" check in the + * last bullet, is to have the Relying Party generate promptText and store it + * along the nonce in the extraData blob. + */ +public class ConfirmationDialog { + private static final String TAG = "ConfirmationDialog"; + + private CharSequence mPromptText; + private byte[] mExtraData; + private ConfirmationCallback mCallback; + private Executor mExecutor; + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + private void doCallback(int responseCode, byte[] dataThatWasConfirmed, + ConfirmationCallback callback) { + switch (responseCode) { + case KeyStore.CONFIRMATIONUI_OK: + callback.onConfirmedByUser(dataThatWasConfirmed); + break; + + case KeyStore.CONFIRMATIONUI_CANCELED: + callback.onDismissedByUser(); + break; + + case KeyStore.CONFIRMATIONUI_ABORTED: + callback.onDismissedByApplication(); + break; + + case KeyStore.CONFIRMATIONUI_SYSTEM_ERROR: + callback.onError(new Exception("System error returned by ConfirmationUI.")); + break; + + default: + callback.onError(new Exception("Unexpected responseCode=" + responseCode + + " from onConfirmtionPromptCompleted() callback.")); + break; + } + } + + private final android.os.IBinder mCallbackBinder = + new android.security.IConfirmationPromptCallback.Stub() { + @Override + public void onConfirmationPromptCompleted( + int responseCode, final byte[] dataThatWasConfirmed) + throws android.os.RemoteException { + if (mCallback != null) { + ConfirmationCallback callback = mCallback; + Executor executor = mExecutor; + mCallback = null; + mExecutor = null; + if (executor == null) { + doCallback(responseCode, dataThatWasConfirmed, callback); + } else { + executor.execute(new Runnable() { + @Override + public void run() { + doCallback(responseCode, dataThatWasConfirmed, callback); + } + }); + } + } + } + }; + + /** + * A builder that collects arguments, to be shown on the system-provided confirmation dialog. + */ + public static class Builder { + + private CharSequence mPromptText; + private byte[] mExtraData; + + /** + * Creates a builder for the confirmation dialog. + */ + public Builder() { + } + + /** + * Sets the prompt text for the dialog. + * + * @param promptText the text to present in the prompt. + * @return the builder. + */ + public Builder setPromptText(CharSequence promptText) { + mPromptText = promptText; + return this; + } + + /** + * Sets the extra data for the dialog. + * + * @param extraData data to include in the response data. + * @return the builder. + */ + public Builder setExtraData(byte[] extraData) { + mExtraData = extraData; + return this; + } + + /** + * Creates a {@link ConfirmationDialog} with the arguments supplied to this builder. + * + * @param context the application context + * @return a {@link ConfirmationDialog} + * @throws IllegalArgumentException if any of the required fields are not set. + */ + public ConfirmationDialog build(Context context) { + 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(mPromptText, mExtraData); + } + } + + private ConfirmationDialog(CharSequence promptText, byte[] extraData) { + mPromptText = promptText; + mExtraData = extraData; + } + + /** + * Requests a confirmation prompt to be presented to the user. + * + * When the prompt is no longer being presented, one of the methods in + * {@link ConfirmationCallback} is called on the supplied callback object. + * + * @param executor the executor identifying the thread that will receive the callback. + * @param callback the callback to use when the dialog 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. + */ + public void presentPrompt(@NonNull Executor executor, @NonNull ConfirmationCallback callback) + throws ConfirmationAlreadyPresentingException, + ConfirmationNotAvailableException { + if (mCallback != null) { + throw new ConfirmationAlreadyPresentingException(); + } + mCallback = callback; + mExecutor = executor; + + int uiOptionsAsFlags = 0; + // TODO: set AccessibilityInverted, AccessibilityMagnified in uiOptionsAsFlags as needed. + String locale = Locale.getDefault().toLanguageTag(); + int responseCode = mKeyStore.presentConfirmationPrompt( + mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags); + switch (responseCode) { + case KeyStore.CONFIRMATIONUI_OK: + return; + + case KeyStore.CONFIRMATIONUI_OPERATION_PENDING: + throw new ConfirmationAlreadyPresentingException(); + + case KeyStore.CONFIRMATIONUI_UNIMPLEMENTED: + throw new ConfirmationNotAvailableException(); + + case KeyStore.CONFIRMATIONUI_UIERROR: + throw new IllegalArgumentException(); + + default: + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from presentConfirmationPrompt() call."); + throw new IllegalArgumentException(); + } + } + + /** + * Cancels a prompt currently being displayed. + * + * On success, the + * {@link ConfirmationCallback#onDismissedByApplication onDismissedByApplication()} method on + * the supplied callback object will be called asynchronously. + * + * @throws IllegalStateException if no prompt is currently being presented. + */ + public void cancelPrompt() { + int responseCode = mKeyStore.cancelConfirmationPrompt(mCallbackBinder); + if (responseCode == KeyStore.CONFIRMATIONUI_OK) { + return; + } else if (responseCode == KeyStore.CONFIRMATIONUI_OPERATION_PENDING) { + throw new IllegalStateException(); + } else { + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from cancelConfirmationPrompt() call."); + throw new IllegalStateException(); + } + } + + /** + * Checks if the device supports confirmation prompts. + * + * @return true if confirmation prompts are supported by the device. + */ + public static boolean isSupported() { + // TODO: read and return system property. + return true; + } +} diff --git a/core/java/android/security/ConfirmationNotAvailableException.java b/core/java/android/security/ConfirmationNotAvailableException.java new file mode 100644 index 0000000000000..8d0e672c0671e --- /dev/null +++ b/core/java/android/security/ConfirmationNotAvailableException.java @@ -0,0 +1,30 @@ +/* + * Copyright 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; + +/** + * This exception is thrown when presenting a prompt fails because the the environment lacks + * facilities for showing confirmations. + */ +public class ConfirmationNotAvailableException extends Exception { + public ConfirmationNotAvailableException() { + } + + public ConfirmationNotAvailableException(String message) { + super(message); + } +} diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 3464370328450..1d13335043508 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -74,6 +74,7 @@ public final class KeymasterDefs { public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505; public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506; public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507; + public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508; public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600; public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601; diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index f721ed3af7ba4..09b3b9b523b42 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -248,7 +248,8 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { spec.getUserAuthenticationValidityDurationSeconds(), spec.isUserAuthenticationValidWhileOnBody(), spec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + spec.isUserConfirmationRequired()); } catch (IllegalStateException | IllegalArgumentException e) { throw new InvalidAlgorithmParameterException(e); } @@ -289,7 +290,8 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { spec.getUserAuthenticationValidityDurationSeconds(), spec.isUserAuthenticationValidWhileOnBody(), spec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + spec.isUserConfirmationRequired()); if (spec.isTrustedUserPresenceRequired()) { args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index d1eb6888bbfdc..e33e3cd4e92b3 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -349,7 +349,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mSpec.getUserAuthenticationValidityDurationSeconds(), mSpec.isUserAuthenticationValidWhileOnBody(), mSpec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + mSpec.isUserConfirmationRequired()); } catch (IllegalArgumentException | IllegalStateException e) { throw new InvalidAlgorithmParameterException(e); } @@ -545,7 +546,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mSpec.getUserAuthenticationValidityDurationSeconds(), mSpec.isUserAuthenticationValidWhileOnBody(), mSpec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + mSpec.isUserConfirmationRequired()); args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()); args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, mSpec.getKeyValidityForOriginationEnd()); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index 9df37f58fd091..7bbc099645845 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -190,6 +190,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { && !keymasterSecureUserIds.contains(getGateKeeperSecureUserId()); } + boolean userConfirmationRequired = keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); + return new KeyInfo(entryAlias, insideSecureHardware, origin, @@ -207,7 +209,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { userAuthenticationRequirementEnforcedBySecureHardware, userAuthenticationValidWhileOnBody, trustedUserPresenceRequred, - invalidatedByBiometricEnrollment); + invalidatedByBiometricEnrollment, + userConfirmationRequired); } private static BigInteger getGateKeeperSecureUserId() throws ProviderException { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index 440e0863fbb1d..05cc74a0bec95 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -502,7 +502,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { spec.getUserAuthenticationValidityDurationSeconds(), spec.isUserAuthenticationValidWhileOnBody(), spec.isInvalidatedByBiometricEnrollment(), - spec.getBoundToSpecificSecureUserId()); + spec.getBoundToSpecificSecureUserId(), + spec.isUserConfirmationRequired()); importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()); importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, @@ -704,7 +705,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { params.getUserAuthenticationValidityDurationSeconds(), params.isUserAuthenticationValidWhileOnBody(), params.isInvalidatedByBiometricEnrollment(), - params.getBoundToSpecificSecureUserId()); + params.getBoundToSpecificSecureUserId(), + params.isUserConfirmationRequired()); KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( args, keymasterAlgorithm, diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index a896c72463fb3..da23c70f58bb7 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -264,6 +264,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mInvalidatedByBiometricEnrollment; private final boolean mIsStrongBoxBacked; + private final boolean mUserConfirmationRequired; /** * @hide should be built with Builder @@ -293,7 +294,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { boolean uniqueIdIncluded, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, - boolean isStrongBoxBacked) { + boolean isStrongBoxBacked, + boolean userConfirmationRequired) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -341,6 +343,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mIsStrongBoxBacked = isStrongBoxBacked; + mUserConfirmationRequired = userConfirmationRequired; } /** @@ -546,6 +549,26 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { return mUserAuthenticationRequired; } + /** + * Returns {@code true} if the key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link Builder#setUserAuthenticationRequired(boolean)}). Keys can be created that require + * confirmation but not user authentication, or user authentication but not confirmation, or + * both. Confirmation verifies that some user with physical possession of the device has + * approved a displayed message. User authentication verifies that the correct user is present + * and has authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see Builder#setUserConfirmationRequired(boolean) + */ + public boolean isUserConfirmationRequired() { + return mUserConfirmationRequired; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required @@ -675,6 +698,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; private boolean mIsStrongBoxBacked = false; + private boolean mUserConfirmationRequired; /** * Creates a new instance of the {@code Builder}. @@ -1062,6 +1086,29 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { return this; } + /** + * Sets whether this key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #setUserAuthenticationRequired(boolean)}). Keys can be created that require + * confirmation but not user authentication, or user authentication but not confirmation, + * or both. Confirmation verifies that some user with physical possession of the device has + * approved a displayed message. User authentication verifies that the correct user is + * present and has authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see {@link android.security.ConfirmationPrompter ConfirmationPrompter} class for + * more details about user confirmations. + */ + @NonNull + public Builder setUserConfirmationRequired(boolean required) { + mUserConfirmationRequired = required; + return this; + } + /** * Sets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect if the key requires user @@ -1249,7 +1296,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { mUniqueIdIncluded, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, - mIsStrongBoxBacked); + mIsStrongBoxBacked, + mUserConfirmationRequired); } } } diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index 864f62a6de04d..0a75cd5268b7b 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -82,6 +82,7 @@ public class KeyInfo implements KeySpec { private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mTrustedUserPresenceRequired; private final boolean mInvalidatedByBiometricEnrollment; + private final boolean mUserConfirmationRequired; /** * @hide @@ -103,7 +104,8 @@ public class KeyInfo implements KeySpec { boolean userAuthenticationRequirementEnforcedBySecureHardware, boolean userAuthenticationValidWhileOnBody, boolean trustedUserPresenceRequired, - boolean invalidatedByBiometricEnrollment) { + boolean invalidatedByBiometricEnrollment, + boolean userConfirmationRequired) { mKeystoreAlias = keystoreKeyAlias; mInsideSecureHardware = insideSecureHardware; mOrigin = origin; @@ -125,6 +127,7 @@ public class KeyInfo implements KeySpec { mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mTrustedUserPresenceRequired = trustedUserPresenceRequired; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; + mUserConfirmationRequired = userConfirmationRequired; } /** @@ -259,6 +262,27 @@ public class KeyInfo implements KeySpec { return mUserAuthenticationRequired; } + /** + * Returns {@code true} if the key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #isUserAuthenticationRequired()}). Keys can be created that require confirmation but + * not user authentication, or user authentication but not confirmation, or both. Confirmation + * verifies that some user with physical possession of the device has approved a displayed + * message. User authentication verifies that the correct user is present and has + * authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see KeyGenParameterSpec.Builder#setUserConfirmationRequired(boolean) + * @see KeyProtection.Builder#setUserConfirmationRequired(boolean) + */ + public boolean isUserConfirmationRequired() { + return mUserConfirmationRequired; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index dbacb9c53dd68..b5b328192f211 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -228,6 +228,7 @@ public final class KeyProtection implements ProtectionParameter { private final boolean mInvalidatedByBiometricEnrollment; private final long mBoundToSecureUserId; private final boolean mCriticalToDeviceEncryption; + private final boolean mUserConfirmationRequired; private KeyProtection( Date keyValidityStart, @@ -244,7 +245,8 @@ public final class KeyProtection implements ProtectionParameter { boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, long boundToSecureUserId, - boolean criticalToDeviceEncryption) { + boolean criticalToDeviceEncryption, + boolean userConfirmationRequired) { mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); @@ -262,6 +264,7 @@ public final class KeyProtection implements ProtectionParameter { mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mBoundToSecureUserId = boundToSecureUserId; mCriticalToDeviceEncryption = criticalToDeviceEncryption; + mUserConfirmationRequired = userConfirmationRequired; } /** @@ -395,6 +398,26 @@ public final class KeyProtection implements ProtectionParameter { return mUserAuthenticationRequired; } + /** + * Returns {@code true} if the key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #isUserAuthenticationRequired()}). Keys can be created that require confirmation but + * not user authentication, or user authentication but not confirmation, or both. Confirmation + * verifies that some user with physical possession of the device has approved a displayed + * message. User authentication verifies that the correct user is present and has + * authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see Builder#setUserConfirmationRequired(boolean) + */ + public boolean isUserConfirmationRequired() { + return mUserConfirmationRequired; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required @@ -488,6 +511,7 @@ public final class KeyProtection implements ProtectionParameter { private int mUserAuthenticationValidityDurationSeconds = -1; private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; + private boolean mUserConfirmationRequired; private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID; private boolean mCriticalToDeviceEncryption = false; @@ -718,6 +742,29 @@ public final class KeyProtection implements ProtectionParameter { return this; } + /** + * Sets whether this key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #setUserAuthenticationRequired(boolean)}). Keys can be created that require + * confirmation but not user authentication, or user authentication but not confirmation, + * or both. Confirmation verifies that some user with physical possession of the device has + * approved a displayed message. User authentication verifies that the correct user is + * present and has authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see {@link android.security.ConfirmationPrompter ConfirmationPrompter} class for + * more details about user confirmations. + */ + @NonNull + public Builder setUserConfirmationRequired(boolean required) { + mUserConfirmationRequired = required; + return this; + } + /** * Sets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect if the key requires user @@ -866,7 +913,8 @@ public final class KeyProtection implements ProtectionParameter { mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, mBoundToSecureUserId, - mCriticalToDeviceEncryption); + mCriticalToDeviceEncryption, + mUserConfirmationRequired); } } } diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index 34c8d1f75f603..4e28601f17a11 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -16,6 +16,7 @@ package android.security.keystore; +import android.util.Log; import android.hardware.fingerprint.FingerprintManager; import android.security.GateKeeper; import android.security.KeyStore; @@ -93,6 +94,8 @@ public abstract class KeymasterUtils { * overriding the default logic in this method where the key is bound to either the root * SID of the current user, or the fingerprint SID if explicit fingerprint authorization * is requested. + * @param userConfirmationRequired whether user confirmation is required to authorize the use + * of the key. * @throws IllegalStateException if user authentication is required but the system is in a wrong * state (e.g., secure lock screen not set up) for generating or importing keys that * require user authentication. @@ -102,7 +105,12 @@ public abstract class KeymasterUtils { int userAuthenticationValidityDurationSeconds, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, - long boundToSpecificSecureUserId) { + long boundToSpecificSecureUserId, + boolean userConfirmationRequired) { + if (userConfirmationRequired) { + args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); + } + if (!userAuthenticationRequired) { args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); return;