Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ca6272cf authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Keystore 2.0: Android Protected Confirmation" am: 30841f17 am: 3443d2b6

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1508897

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I034d45f1afcdd07c0b12e2db7e4134b695ec5c70
parents 5f2d59fd 3443d2b6
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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",
+157 −36
Original line number Diff line number Diff line
@@ -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,8 +348,31 @@ public class ConfirmationPrompt {
        mCallback = callback;
        mExecutor = executor;

        int uiOptionsAsFlags = getUiOptionsAsFlags();
        String locale = Locale.getDefault().toLanguageTag();
        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();

                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) {
@@ -295,6 +396,7 @@ public class ConfirmationPrompt {
                    throw new IllegalArgumentException();
            }
        }
    }

    /**
     * Cancels a prompt currently being displayed.
@@ -306,6 +408,21 @@ public class ConfirmationPrompt {
     * @throws IllegalStateException if no prompt is currently being presented.
     */
    public void cancelPrompt() {
        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 {
            int responseCode = mKeyStore.cancelConfirmationPrompt(mCallbackBinder);
            if (responseCode == KeyStore.CONFIRMATIONUI_OK) {
                return;
@@ -319,6 +436,7 @@ public class ConfirmationPrompt {
                throw new IllegalStateException();
            }
        }
    }

    /**
     * Checks if the device supports confirmation prompts.
@@ -330,6 +448,9 @@ public class ConfirmationPrompt {
        if (isAccessibilityServiceRunning(context)) {
            return false;
        }
        if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
            return new AndroidProtectedConfirmation().isConfirmationPromptSupported();
        }
        return KeyStore.getInstance().isConfirmationPromptSupported();
    }
}
+118 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }

}