diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2021-01-05 23:29:35 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-01-05 23:29:35 +0000 |
commit | 30841f177c920b81d163a1c59e33c3e0e319532d (patch) | |
tree | e550e3e2e69e0c2162e2391ed79c09b4d5dff3f9 | |
parent | 7f2de331ac0005d3874ec8598155d21d92a62bf7 (diff) | |
parent | 81d75b4c5276f67921d199ff0dfac1f0bb19ca67 (diff) |
Merge "Keystore 2.0: Android Protected Confirmation"
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | core/java/android/security/ConfirmationPrompt.java | 193 | ||||
-rw-r--r-- | keystore/java/android/security/AndroidProtectedConfirmation.java | 118 |
3 files changed, 276 insertions, 36 deletions
diff --git a/Android.bp b/Android.bp index 36c2fa8762a4..317b6746f9e1 100644 --- a/Android.bp +++ b/Android.bp @@ -481,6 +481,7 @@ java_library { "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", + "android.security.apc-java", "android.system.keystore2-java", "android.system.suspend.control.internal-java", "devicepolicyprotosnano", diff --git a/core/java/android/security/ConfirmationPrompt.java b/core/java/android/security/ConfirmationPrompt.java index f67af85d00e3..232903724d82 100644 --- a/core/java/android/security/ConfirmationPrompt.java +++ b/core/java/android/security/ConfirmationPrompt.java @@ -21,6 +21,7 @@ import android.content.ContentResolver; import android.content.Context; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; +import android.security.keystore.AndroidKeyStoreProvider; import android.text.TextUtils; import android.util.Log; @@ -36,15 +37,15 @@ import java.util.concurrent.Executor; * compromised. Implementing confirmation prompts with these guarantees requires dedicated * hardware-support and may not always be available. * - * <p>Confirmation prompts are typically used with an external entitity - the <i>Relying Party</i> - + * <p>Confirmation prompts are typically used with an external entity - the <i>Relying Party</i> - * in the following way. The setup steps are as follows: * <ul> * <li> Before first use, the application generates a key-pair with the * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserConfirmationRequired - * CONFIRMATION tag} set. Device attestation, - * e.g. {@link java.security.KeyStore#getCertificateChain getCertificateChain()}, is used to - * generate a certificate chain that includes the public key (<code>Kpub</code> in the following) - * of the newly generated key. + * CONFIRMATION tag} set. AndroidKeyStore key attestation, e.g., + * {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])} + * is used to generate a certificate chain that includes the public key (<code>Kpub</code> in the + * following) of the newly generated key. * <li> The application sends <code>Kpub</code> and the certificate chain resulting from device * attestation to the <i>Relying Party</i>. * <li> The <i>Relying Party</i> validates the certificate chain which involves checking the root @@ -78,9 +79,10 @@ import java.util.concurrent.Executor; * previously created nonce. If all checks passes, the transaction is executed. * </ul> * - * <p>A common way of implementing the "<code>promptText</code> is what is expected" check in the - * 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. + * <p>Note: It is vital to check the <code>promptText</code> because this is the only part that + * the user has approved. To avoid writing parsers for all of the possible locales, it is + * recommended that the <i>Relying Party</i> uses the same string generator as used on the device + * and performs a simple string comparison. */ public class ConfirmationPrompt { private static final String TAG = "ConfirmationPrompt"; @@ -92,6 +94,14 @@ public class ConfirmationPrompt { private Context mContext; private final KeyStore mKeyStore = KeyStore.getInstance(); + private AndroidProtectedConfirmation mProtectedConfirmation; + + private AndroidProtectedConfirmation getService() { + if (mProtectedConfirmation == null) { + mProtectedConfirmation = new AndroidProtectedConfirmation(); + } + return mProtectedConfirmation; + } private void doCallback(int responseCode, byte[] dataThatWasConfirmed, ConfirmationCallback callback) { @@ -119,6 +129,32 @@ public class ConfirmationPrompt { } } + private void doCallback2(int responseCode, byte[] dataThatWasConfirmed, + ConfirmationCallback callback) { + switch (responseCode) { + case AndroidProtectedConfirmation.ERROR_OK: + callback.onConfirmed(dataThatWasConfirmed); + break; + + case AndroidProtectedConfirmation.ERROR_CANCELED: + callback.onDismissed(); + break; + + case AndroidProtectedConfirmation.ERROR_ABORTED: + callback.onCanceled(); + break; + + case AndroidProtectedConfirmation.ERROR_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 @@ -144,6 +180,29 @@ public class ConfirmationPrompt { } }; + private final android.security.apc.IConfirmationCallback mConfirmationCallback = + new android.security.apc.IConfirmationCallback.Stub() { + @Override + public void onCompleted(int result, byte[] dataThatWasConfirmed) + throws android.os.RemoteException { + if (mCallback != null) { + ConfirmationCallback callback = mCallback; + Executor executor = mExecutor; + mCallback = null; + mExecutor = null; + if (executor == null) { + doCallback2(result, dataThatWasConfirmed, callback); + } else { + executor.execute(new Runnable() { + @Override public void run() { + doCallback2(result, dataThatWasConfirmed, callback); + } + }); + } + } + } + }; + /** * A builder that collects arguments, to be shown on the system-provided confirmation prompt. */ @@ -211,6 +270,9 @@ public class ConfirmationPrompt { private static final int UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG = 1 << 1; private int getUiOptionsAsFlags() { + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + return getUiOptionsAsFlags2(); + } int uiOptionsAsFlags = 0; ContentResolver contentResolver = mContext.getContentResolver(); int inversionEnabled = Settings.Secure.getInt(contentResolver, @@ -226,6 +288,22 @@ public class ConfirmationPrompt { return uiOptionsAsFlags; } + private int getUiOptionsAsFlags2() { + int uiOptionsAsFlags = 0; + ContentResolver contentResolver = mContext.getContentResolver(); + int inversionEnabled = Settings.Secure.getInt(contentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0); + if (inversionEnabled == 1) { + uiOptionsAsFlags |= AndroidProtectedConfirmation.FLAG_UI_OPTION_INVERTED; + } + float fontScale = Settings.System.getFloat(contentResolver, + Settings.System.FONT_SCALE, (float) 1.0); + if (fontScale > 1.0) { + uiOptionsAsFlags |= AndroidProtectedConfirmation.FLAG_UI_OPTION_MAGNIFIED; + } + return uiOptionsAsFlags; + } + private static boolean isAccessibilityServiceRunning(Context context) { boolean serviceRunning = false; try { @@ -270,29 +348,53 @@ public class ConfirmationPrompt { mCallback = callback; mExecutor = executor; - int uiOptionsAsFlags = getUiOptionsAsFlags(); String locale = Locale.getDefault().toLanguageTag(); - int responseCode = mKeyStore.presentConfirmationPrompt( - mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags); - switch (responseCode) { - case KeyStore.CONFIRMATIONUI_OK: - return; + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + int uiOptionsAsFlags = getUiOptionsAsFlags2(); + int responseCode = getService().presentConfirmationPrompt( + mConfirmationCallback, mPromptText.toString(), mExtraData, locale, + uiOptionsAsFlags); + switch (responseCode) { + case AndroidProtectedConfirmation.ERROR_OK: + return; + + case AndroidProtectedConfirmation.ERROR_OPERATION_PENDING: + throw new ConfirmationAlreadyPresentingException(); + + case AndroidProtectedConfirmation.ERROR_UNIMPLEMENTED: + throw new ConfirmationNotAvailableException(); - case KeyStore.CONFIRMATIONUI_OPERATION_PENDING: - throw new ConfirmationAlreadyPresentingException(); + default: + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from presentConfirmationPrompt() call."); + throw new IllegalArgumentException(); + } + } else { + int uiOptionsAsFlags = getUiOptionsAsFlags(); + int responseCode = mKeyStore.presentConfirmationPrompt( + mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags); + switch (responseCode) { + case KeyStore.CONFIRMATIONUI_OK: + return; - case KeyStore.CONFIRMATIONUI_UNIMPLEMENTED: - throw new ConfirmationNotAvailableException(); + case KeyStore.CONFIRMATIONUI_OPERATION_PENDING: + throw new ConfirmationAlreadyPresentingException(); - case KeyStore.CONFIRMATIONUI_UIERROR: - throw new IllegalArgumentException(); + case KeyStore.CONFIRMATIONUI_UNIMPLEMENTED: + throw new ConfirmationNotAvailableException(); - default: - // Unexpected error code. - Log.w(TAG, - "Unexpected responseCode=" + responseCode - + " from presentConfirmationPrompt() call."); - throw new IllegalArgumentException(); + case KeyStore.CONFIRMATIONUI_UIERROR: + throw new IllegalArgumentException(); + + default: + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from presentConfirmationPrompt() call."); + throw new IllegalArgumentException(); + } } } @@ -306,17 +408,33 @@ public class ConfirmationPrompt { * @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(); + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + int responseCode = + getService().cancelConfirmationPrompt(mConfirmationCallback); + if (responseCode == AndroidProtectedConfirmation.ERROR_OK) { + return; + } else if (responseCode == AndroidProtectedConfirmation.ERROR_OPERATION_PENDING) { + throw new IllegalStateException(); + } else { + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from cancelConfirmationPrompt() call."); + throw new IllegalStateException(); + } } else { - // Unexpected error code. - Log.w(TAG, - "Unexpected responseCode=" + responseCode - + " from cancelConfirmationPrompt() call."); - throw new IllegalStateException(); + 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(); + } } } @@ -330,6 +448,9 @@ public class ConfirmationPrompt { if (isAccessibilityServiceRunning(context)) { return false; } + if (AndroidKeyStoreProvider.isKeystore2Enabled()) { + return new AndroidProtectedConfirmation().isConfirmationPromptSupported(); + } return KeyStore.getInstance().isConfirmationPromptSupported(); } } diff --git a/keystore/java/android/security/AndroidProtectedConfirmation.java b/keystore/java/android/security/AndroidProtectedConfirmation.java new file mode 100644 index 000000000000..dfe485ac8274 --- /dev/null +++ b/keystore/java/android/security/AndroidProtectedConfirmation.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 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.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.security.apc.IConfirmationCallback; +import android.security.apc.IProtectedConfirmation; +import android.security.apc.ResponseCode; +import android.util.Log; + +/** + * @hide + */ +public class AndroidProtectedConfirmation { + private static final String TAG = "AndroidProtectedConfirmation"; + + public static final int ERROR_OK = ResponseCode.OK; + public static final int ERROR_CANCELED = ResponseCode.CANCELLED; + public static final int ERROR_ABORTED = ResponseCode.ABORTED; + public static final int ERROR_OPERATION_PENDING = ResponseCode.OPERATION_PENDING; + public static final int ERROR_IGNORED = ResponseCode.IGNORED; + public static final int ERROR_SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR; + public static final int ERROR_UNIMPLEMENTED = ResponseCode.UNIMPLEMENTED; + + public static final int FLAG_UI_OPTION_INVERTED = + IProtectedConfirmation.FLAG_UI_OPTION_INVERTED; + public static final int FLAG_UI_OPTION_MAGNIFIED = + IProtectedConfirmation.FLAG_UI_OPTION_MAGNIFIED; + + private IProtectedConfirmation mProtectedConfirmation; + + public AndroidProtectedConfirmation() { + mProtectedConfirmation = null; + } + + private synchronized IProtectedConfirmation getService() { + if (mProtectedConfirmation == null) { + mProtectedConfirmation = IProtectedConfirmation.Stub.asInterface(ServiceManager + .getService("android.security.apc")); + } + return mProtectedConfirmation; + } + + /** + * Requests keystore call into the confirmationui HAL to display a prompt. + * + * @param listener the binder to use for callbacks. + * @param promptText the prompt to display. + * @param extraData extra data / nonce from application. + * @param locale the locale as a BCP 47 language tag. + * @param uiOptionsAsFlags the UI options to use, as flags. + * @return one of the {@code CONFIRMATIONUI_*} constants, for + * example {@code KeyStore.CONFIRMATIONUI_OK}. + */ + public int presentConfirmationPrompt(IConfirmationCallback listener, String promptText, + byte[] extraData, String locale, int uiOptionsAsFlags) { + try { + getService().presentPrompt(listener, promptText, extraData, locale, + uiOptionsAsFlags); + return ERROR_OK; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return ERROR_SYSTEM_ERROR; + } catch (ServiceSpecificException e) { + return e.errorCode; + } + } + + /** + * Requests keystore call into the confirmationui HAL to cancel displaying a prompt. + * + * @param listener the binder passed to the {@link #presentConfirmationPrompt} method. + * @return one of the {@code CONFIRMATIONUI_*} constants, for + * example {@code KeyStore.CONFIRMATIONUI_OK}. + */ + public int cancelConfirmationPrompt(IConfirmationCallback listener) { + try { + getService().cancelPrompt(listener); + return ERROR_OK; + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return ERROR_SYSTEM_ERROR; + } catch (ServiceSpecificException e) { + return e.errorCode; + } + } + + /** + * Requests keystore to check if the confirmationui HAL is available. + * + * @return whether the confirmationUI HAL is available. + */ + public boolean isConfirmationPromptSupported() { + try { + return getService().isSupported(); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + +} |