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: + *
Kpub in the following)
+ * of the newly generated key.
+ * Kpub and the certificate chain resulting from device
+ * attestation to the Relying Party.
+ * Kpub, and that the attestation certificate
+ * asserts that Kpub has the
+ * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserConfirmationRequired
+ * CONFIRMATION tag} set.
+ * Additionally the relying party stores Kpub and associates it with the device
+ * it was received from.
+ * 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: + *
extraData (via the Builder helper class) to the
+ * {@link #presentPrompt presentPrompt()} method. The Relying Party stores the nonce locally
+ * since it'll use it in a later step.
+ * dataThatWasConfirmed parameter. This blob contains the text that was shown to the
+ * user, the extraData parameter, and possibly other data.
+ * Kpub and then
+ * extracts promptText matches what is expected and extraData matches the
+ * previously created nonce. If all checks passes, the transaction is executed.
+ * 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;