diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 5f5f95db64..20ba9a9fa1 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -207,6 +207,14 @@ default + + android.hardware.confirmationui + 1 + + IConfirmationUI + default + + android.hardware.contexthub diff --git a/confirmationui/OWNERS b/confirmationui/OWNERS new file mode 100644 index 0000000000..2bcdb0e3c6 --- /dev/null +++ b/confirmationui/OWNERS @@ -0,0 +1,2 @@ +swillden@google.com +subrahmanyaman@google.com diff --git a/confirmationui/aidl/Android.bp b/confirmationui/aidl/Android.bp new file mode 100644 index 0000000000..4ba4acd0d0 --- /dev/null +++ b/confirmationui/aidl/Android.bp @@ -0,0 +1,38 @@ +// Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +aidl_interface { + name: "android.hardware.confirmationui", + vendor_available: true, + imports: [ + "android.hardware.security.keymint-V2", + ], + srcs: ["android/hardware/confirmationui/*.aidl"], + stability: "vintf", + backend: { + java: { + platform_apis: true, + }, + ndk: { + apps_enabled: false, + }, + cpp: { + enabled: false, + }, + }, +} diff --git a/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationResultCallback.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationResultCallback.aidl new file mode 100644 index 0000000000..6d6ec97377 --- /dev/null +++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationResultCallback.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.confirmationui; +@VintfStability +interface IConfirmationResultCallback { + void result(in int error, in byte[] formattedMessage, in byte[] confirmationToken); +} diff --git a/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationUI.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationUI.aidl new file mode 100644 index 0000000000..c5c7aa7c16 --- /dev/null +++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationUI.aidl @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.confirmationui; +@VintfStability +interface IConfirmationUI { + void abort(); + void deliverSecureInputEvent(in android.hardware.security.keymint.HardwareAuthToken secureInputToken); + void promptUserConfirmation(in android.hardware.confirmationui.IConfirmationResultCallback resultCB, in byte[] promptText, in byte[] extraData, in @utf8InCpp String locale, in android.hardware.confirmationui.UIOption[] uiOptions); + const int OK = 0; + const int CANCELED = 1; + const int ABORTED = 2; + const int OPERATION_PENDING = 3; + const int IGNORED = 4; + const int SYSTEM_ERROR = 5; + const int UNIMPLEMENTED = 6; + const int UNEXPECTED = 7; + const int UI_ERROR = 65536; + const int UI_ERROR_MISSING_GLYPH = 65537; + const int UI_ERROR_MESSAGE_TOO_LONG = 65538; + const int UI_ERROR_MALFORMED_UTF8ENCODING = 65539; + const int TEST_KEY_BYTE = 165; + const int MAX_MESSAGE_SIZE = 6144; +} diff --git a/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/TestModeCommands.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/TestModeCommands.aidl new file mode 100644 index 0000000000..b8629d1321 --- /dev/null +++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/TestModeCommands.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.confirmationui; +@Backing(type="int") @VintfStability +enum TestModeCommands { + OK_EVENT = 0, + CANCEL_EVENT = 1, +} diff --git a/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/UIOption.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/UIOption.aidl new file mode 100644 index 0000000000..ebecf9e295 --- /dev/null +++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/UIOption.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.confirmationui; +@Backing(type="int") @VintfStability +enum UIOption { + ACCESSIBILITY_INVERTED = 0, + ACCESSIBILITY_MAGNIFIED = 1, +} diff --git a/confirmationui/aidl/android/hardware/confirmationui/IConfirmationResultCallback.aidl b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationResultCallback.aidl new file mode 100644 index 0000000000..2165fdd5ae --- /dev/null +++ b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationResultCallback.aidl @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 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.hardware.confirmationui; + +import android.hardware.confirmationui.IConfirmationUI; + +/** + * Callback interface passed to IConfirmationUI::promptUserConfirmation(). + * Informs the caller about the result of the prompt operation. + */ +@VintfStability +interface IConfirmationResultCallback { + /** + * This callback is called by the confirmation provider when it stops prompting the user. + * Iff the user has confirmed the prompted text, error is ErrorCode::OK and the + * parameters formattedMessage and confirmationToken hold the values needed to request + * a signature from keymaster. + * In all other cases formattedMessage and confirmationToken must be of length 0. + * + * @param error - OK: IFF the user has confirmed the prompt. + * - CANCELED: If the user has pressed the cancel button. + * - ABORTED: If IConfirmationUI::abort() was called. + * - SYSTEM_ERROR: If an unexpected System error occurred that prevented the TUI + * from being shut down gracefully. + * + * @param formattedMessage holds the prompt text and extra data. + * The message is CBOR (RFC 7049) encoded and has the following format: + * CBOR_MAP{ "prompt", , "extra", } + * The message is a CBOR encoded map (type 5) with the keys + * "prompt" and "extra". The keys are encoded as CBOR text string + * (type 3). The value is encoded as CBOR text string + * (type 3), and the value is encoded as CBOR byte string + * (type 2). The map must have exactly one key value pair for each of + * the keys "prompt" and "extra". Other keys are not allowed. + * The value of "prompt" is given by the proptText argument to + * IConfirmationUI::promptUserConfirmation and must not be modified + * by the implementation. + * The value of "extra" is given by the extraData argument to + * IConfirmationUI::promptUserConfirmation and must not be modified + * or interpreted by the implementation. + * + * @param confirmationToken a 32-byte HMAC-SHA256 value, computed over + * "confirmation token" || + * i.e. the literal UTF-8 encoded string "confirmation token", without + * the "", concatenated with the formatted message as returned in the + * formattedMessage argument. The HMAC is keyed with a 256-bit secret + * which is shared with Keymaster. In test mode the test key MUST be + * used (see types.hal TestModeCommands and + * IConfirmationUI::TEST_KEY_BYTE). + */ + void result(in int error, in byte[] formattedMessage, in byte[] confirmationToken); +} diff --git a/confirmationui/aidl/android/hardware/confirmationui/IConfirmationUI.aidl b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationUI.aidl new file mode 100644 index 0000000000..f0711265e0 --- /dev/null +++ b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationUI.aidl @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2022 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.hardware.confirmationui; + +import android.hardware.confirmationui.IConfirmationResultCallback; +import android.hardware.confirmationui.UIOption; +import android.hardware.security.keymint.HardwareAuthToken; + +@VintfStability +interface IConfirmationUI { + // Possible Errors. + /** + * API call succeeded or the user gave approval (result callback). + */ + const int OK = 0; + /** + * The user canceled the TUI (result callback). + */ + const int CANCELED = 1; + /** + * IConfirmationUI::abort() was called. (result callback). + */ + const int ABORTED = 2; + /** + * Cannot start another prompt. + */ + const int OPERATION_PENDING = 3; + /** + * IConfirmationUI::deliverSecureInputEvent call was ignored. + */ + const int IGNORED = 4; + /** + * An unexpected system error occurred. + */ + const int SYSTEM_ERROR = 5; + /** + * Returned by an unimplemented API call. + */ + const int UNIMPLEMENTED = 6; + /** + * This is returned when an error is diagnosed that should have been + * caught by earlier input sanitization. Should never be seen in production. + */ + const int UNEXPECTED = 7; + /** + * General UI error. + */ + const int UI_ERROR = 0x10000; + /** + * This is returned if there is no corresponding glyph for a character. + */ + const int UI_ERROR_MISSING_GLYPH = 0x10001; + /** + * The implementation must return this error code on promptUserConfirmation if the + * resulting formatted message does not fit into MAX_MESSAGE_SIZE bytes. It is + * advised that the implementation formats the message upon receiving this API call to + * be able to diagnose this syndrome. + */ + const int UI_ERROR_MESSAGE_TOO_LONG = 0x10002; + /** + * This is returned if the UTF8 encoding is malformed. + */ + const int UI_ERROR_MALFORMED_UTF8ENCODING = 0x10003; + + // Constants + /** + * The test key is 32byte word with all bytes set to TEST_KEY_BYTE. + */ + const int TEST_KEY_BYTE = 0xA5; + /** + * This defines the maximum message size. This indirectly limits the size of the prompt text + * and the extra data that can be passed to the confirmation UI. The prompt text and extra data + * must fit in to this size including CBOR header information. + */ + const int MAX_MESSAGE_SIZE = 0x1800; + + /** + * Aborts a pending user prompt. This allows the framework to gracefully end a TUI dialog. + * If a TUI operation was pending the corresponding call back is informed with + * ErrorCode::Aborted. + */ + void abort(); + + /** + * DeliverSecureInput is used by the framework to deliver a secure input event to the + * confirmation provider. + * + * VTS test mode: + * This function can be used to test certain code paths non-interactively. + * See TestModeCommands.aidl for details. + * + * @param secureInputToken An authentication token as generated by Android authentication + * providers. + */ + void deliverSecureInputEvent(in HardwareAuthToken secureInputToken); + + /** + * Asynchronously initiates a confirmation UI dialog prompting the user to confirm a given text. + * The TUI prompt must be implemented in such a way that a positive response indicates with + * high confidence that a user has seen the given prompt text even if the Android framework + * including the kernel was compromised. + * + * Service status return: + * + * OK IFF the dialog was successfully started. In this case, and only in this + * case, the implementation must, eventually, call the callback to + * indicate completion. + * OPERATION_PENDING is returned when the confirmation provider is currently + * in use. + * SYSTEM_ERROR an error occurred trying to communicate with the confirmation + * provider (e.g. trusted app). + * UI_ERROR the confirmation provider encountered an issue with displaying + * the prompt text to the user. + * + * @param resultCB Implementation of IResultCallback. Used by the implementation to report + * the result of the current pending user prompt. + * + * @param promptText UTF-8 encoded bytes which is to be presented to the user. + * + * @param extraData A binary blob that must be included in the formatted output message as is. + * It is opaque to the implementation. Implementations must neither interpret + * nor modify the content. + * + * @param locale String specifying the locale that must be used by the TUI dialog. The string + * is an IETF BCP 47 tag. + * + * @param uiOptions A set of uiOptions manipulating how the confirmation prompt is displayed. + * Refer to UIOption in types.hal for possible options. + */ + void promptUserConfirmation(in IConfirmationResultCallback resultCB, in byte[] promptText, + in byte[] extraData, in @utf8InCpp String locale, in UIOption[] uiOptions); +} diff --git a/confirmationui/aidl/android/hardware/confirmationui/TestModeCommands.aidl b/confirmationui/aidl/android/hardware/confirmationui/TestModeCommands.aidl new file mode 100644 index 0000000000..5b1d8fbb67 --- /dev/null +++ b/confirmationui/aidl/android/hardware/confirmationui/TestModeCommands.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 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.hardware.confirmationui; + +/** + * Test mode commands. + * + * IConfirmationUI::deliverSecureInputEvent can be used to test certain code paths. + * To that end, the caller passes an auth token that has an HMAC keyed with the test key + * (see IConfirmationUI::TEST_KEY_BYTE). Implementations first check the HMAC against test key. + * If the test key produces a matching HMAC, the implementation evaluates the challenge field + * of the auth token against the values defined in TestModeCommand. + * If the command indicates that a confirmation token is to be generated the test key MUST be used + * to generate this confirmation token. + * + * See command code for individual test command descriptions. + */ +@VintfStability +@Backing(type="int") +enum TestModeCommands { + /** + * Simulates the user pressing the OK button on the UI. If no operation is pending + * ResponseCode::Ignored must be returned. A pending operation is finalized successfully + * see IConfirmationResultCallback::result, however, the test key + * (see IConfirmationUI::TEST_KEY_BYTE) MUST be used to generate the confirmation token. + */ + OK_EVENT = 0, + /** + * Simulates the user pressing the CANCEL button on the UI. If no operation is pending + * Result::Ignored must be returned. A pending operation is finalized as specified in + * IConfirmationResultCallback.hal. + */ + CANCEL_EVENT = 1, +} diff --git a/confirmationui/aidl/android/hardware/confirmationui/UIOption.aidl b/confirmationui/aidl/android/hardware/confirmationui/UIOption.aidl new file mode 100644 index 0000000000..b242c53f4d --- /dev/null +++ b/confirmationui/aidl/android/hardware/confirmationui/UIOption.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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.hardware.confirmationui; + +/** + * UI modification options. + */ +@VintfStability +@Backing(type="int") +enum UIOption { + /** + * Accessibility: Requests color inverted style. + */ + ACCESSIBILITY_INVERTED = 0, + /** + * Accessibility: Requests magnified style. + */ + ACCESSIBILITY_MAGNIFIED = 1, +} diff --git a/confirmationui/aidl/vts/functional/Android.bp b/confirmationui/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..3649c871fa --- /dev/null +++ b/confirmationui/aidl/vts/functional/Android.bp @@ -0,0 +1,45 @@ +// +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_test { + name: "VtsHalConfirmationUITargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "keymint_use_latest_hal_aidl_ndk_shared", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalConfirmationUITargetTest.cpp", + ], + static_libs: [ + "android.hardware.confirmationui-V1-ndk", + "libcn-cbor", + "android.hardware.confirmationui-support-lib", + ], + shared_libs: [ + "libbinder_ndk", + "libcrypto", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/confirmationui/aidl/vts/functional/VtsHalConfirmationUITargetTest.cpp b/confirmationui/aidl/vts/functional/VtsHalConfirmationUITargetTest.cpp new file mode 100644 index 0000000000..bf1f1c890c --- /dev/null +++ b/confirmationui/aidl/vts/functional/VtsHalConfirmationUITargetTest.cpp @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2022 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. + */ + +#define LOG_TAG "ConfirmationIOAidlHalTest" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +static constexpr int TIMEOUT_PERIOD = 10; + +namespace aidl::android::hardware::confirmationui::test { +using ::aidl::android::hardware::security::keymint::HardwareAuthenticatorType; +using ::aidl::android::hardware::security::keymint::HardwareAuthToken; +using ::android::hardware::confirmationui::support::auth_token_key_t; +using ::android::hardware::confirmationui::support::ByteBufferProxy; +using ::android::hardware::confirmationui::support::HMac; +using ::android::hardware::confirmationui::support::hmac_t; +using ::android::hardware::confirmationui::support::hton; +using ::android::hardware::confirmationui::support::NullOr; +using std::shared_ptr; +using std::string; +using std::vector; + +namespace { +const auth_token_key_t testKey(static_cast(IConfirmationUI::TEST_KEY_BYTE)); + +class HMacImplementation { + public: + static NullOr hmac256(const auth_token_key_t& key, + std::initializer_list buffers) { + HMAC_CTX hmacCtx; + HMAC_CTX_init(&hmacCtx); + if (!HMAC_Init_ex(&hmacCtx, key.data(), key.size(), EVP_sha256(), nullptr)) { + return {}; + } + for (auto& buffer : buffers) { + if (!HMAC_Update(&hmacCtx, buffer.data(), buffer.size())) { + return {}; + } + } + hmac_t result; + if (!HMAC_Final(&hmacCtx, result.data(), nullptr)) { + return {}; + } + return result; + } +}; + +using HMacer = HMac; + +template +vector testHMAC(const Data&... data) { + auto hmac = HMacer::hmac256(testKey, data...); + if (!hmac.isOk()) { + ADD_FAILURE() << "Failed to compute test hmac. This is a self-test error."; + return {}; + } + vector result(hmac.value().size()); + std::copy(hmac.value().data(), hmac.value().data() + hmac.value().size(), result.data()); + return result; +} + +template +auto toBytes(const T& v) -> const uint8_t (&)[sizeof(T)] { + return *reinterpret_cast(&v); +} + +HardwareAuthToken makeTestToken(const TestModeCommands command, uint64_t timestamp = 0) { + HardwareAuthToken auth_token; + auth_token.challenge = static_cast(command); + auth_token.userId = 0; + auth_token.authenticatorId = 0; + auth_token.authenticatorType = HardwareAuthenticatorType::NONE; + auth_token.timestamp = {static_cast(timestamp)}; + + // Canonical form of auth-token v0 + // version (1 byte) + // challenge (8 bytes) + // user_id (8 bytes) + // authenticator_id (8 bytes) + // authenticator_type (4 bytes) + // timestamp (8 bytes) + // total 37 bytes + auth_token.mac = testHMAC("\0", + toBytes(auth_token.challenge), // + toBytes(auth_token.userId), // + toBytes(auth_token.authenticatorId), // + toBytes(hton(auth_token.authenticatorType)), // + toBytes(hton(auth_token.timestamp))); // + + return auth_token; +} + +#define DEBUG_CONFRIMATIONUI_UTILS_TEST + +#ifdef DEBUG_CONFRIMATIONUI_UTILS_TEST +std::ostream& hexdump(std::ostream& out, const uint8_t* data, size_t size) { + for (size_t i = 0; i < size; ++i) { + uint8_t byte = data[i]; + out << std::hex << std::setw(2) << std::setfill('0') << (unsigned)byte; + switch (i & 0xf) { + case 0xf: + out << "\n"; + break; + case 7: + out << " "; + break; + default: + out << " "; + break; + } + } + return out; +} +#endif + +constexpr char hex_value[256] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // '0'..'9' + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 'A'..'F' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 'a'..'f' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +std::string hex2str(std::string a) { + std::string b; + size_t num = a.size() / 2; + b.resize(num); + for (size_t i = 0; i < num; i++) { + b[i] = (hex_value[a[i * 2] & 0xFF] << 4) + (hex_value[a[i * 2 + 1] & 0xFF]); + } + return b; +} + +int getReturnCode(const ::ndk::ScopedAStatus& result) { + if (result.isOk()) return IConfirmationUI::OK; + + if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) { + return static_cast(result.getServiceSpecificError()); + } + return result.getStatus(); +} + +} // namespace + +class ConfirmationUIAidlTest : public ::testing::TestWithParam { + public: + void TearDown() override { confirmator_->abort(); } + void SetUp() override { + std::string name = GetParam(); + ASSERT_TRUE(AServiceManager_isDeclared(name.c_str())) << name; + ndk::SpAIBinder binder(AServiceManager_waitForService(name.c_str())); + ASSERT_NE(binder, nullptr); + confirmator_ = IConfirmationUI::fromBinder(binder); + ASSERT_NE(confirmator_, nullptr); + } + + // Used as a mechanism to inform the test about data/event callback + inline void notify() { + std::unique_lock lock(mtx_); + cv_.notify_one(); + } + + // Test code calls this function to wait for data/event callback + inline std::cv_status wait() { + std::unique_lock lock(mtx_); + auto now = std::chrono::system_clock::now(); + std::cv_status status = cv_.wait_until(lock, now + std::chrono::seconds(TIMEOUT_PERIOD)); + return status; + } + + protected: + shared_ptr confirmator_; + + private: + // synchronization objects + std::mutex mtx_; + std::condition_variable cv_; +}; + +class ConfirmationTestCallback + : public ::aidl::android::hardware::confirmationui::BnConfirmationResultCallback { + public: + ConfirmationTestCallback(ConfirmationUIAidlTest& parent) : parent_(parent){}; + virtual ~ConfirmationTestCallback() = default; + + ::ndk::ScopedAStatus result(int32_t err, const vector& msg, + const vector& confToken) override { + error_ = err; + formattedMessage_ = msg; + confirmationToken_ = confToken; + parent_.notify(); + return ndk::ScopedAStatus::ok(); + } + + bool verifyConfirmationToken() { + static constexpr char confirmationPrefix[] = "confirmation token"; + EXPECT_EQ(32U, confirmationToken_.size()); + return 32U == confirmationToken_.size() && + !memcmp(confirmationToken_.data(), + testHMAC(confirmationPrefix, formattedMessage_).data(), 32); + } + + int error_; + vector formattedMessage_; + vector confirmationToken_; + + private: + ConfirmationUIAidlTest& parent_; +}; + +struct CnCborDeleter { + void operator()(cn_cbor* ptr) { cn_cbor_free(ptr); } +}; + +typedef std::unique_ptr CnCborPtr; + +// Simulates the User taping Ok +TEST_P(ConfirmationUIAidlTest, UserOkTest) { + static constexpr char test_prompt[] = "Me first, gimme gimme!"; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk()); + // Simulate the user taping ok. + ASSERT_TRUE(confirmator_->deliverSecureInputEvent(makeTestToken(TestModeCommands::OK_EVENT)) + .isOk()); + // Wait for the callback. + EXPECT_EQ(std::cv_status::no_timeout, wait()); + ASSERT_EQ(IConfirmationUI::OK, conf_cb->error_); + + ASSERT_TRUE(conf_cb->verifyConfirmationToken()); + + cn_cbor_errback cn_cbor_error; + auto parsed_message = CnCborPtr(cn_cbor_decode( + conf_cb->formattedMessage_.data(), conf_cb->formattedMessage_.size(), &cn_cbor_error)); + // is parsable CBOR + ASSERT_TRUE(parsed_message.get()); + // is a map + ASSERT_EQ(CN_CBOR_MAP, parsed_message->type); + + // the message must have exactly 2 key value pairs. + // cn_cbor holds 2* in the length field + ASSERT_EQ(4, parsed_message->length); + // map has key "prompt" + auto prompt = cn_cbor_mapget_string(parsed_message.get(), "prompt"); + ASSERT_TRUE(prompt); + ASSERT_EQ(CN_CBOR_TEXT, prompt->type); + ASSERT_EQ(22, prompt->length); + ASSERT_EQ(0, memcmp(test_prompt, prompt->v.str, 22)); + // map has key "extra" + auto extra_out = cn_cbor_mapget_string(parsed_message.get(), "extra"); + ASSERT_TRUE(extra_out); + ASSERT_EQ(CN_CBOR_BYTES, extra_out->type); + ASSERT_EQ(3, extra_out->length); + ASSERT_EQ(0, memcmp(test_extra, extra_out->v.bytes, 3)); +} + +// Initiates a confirmation prompt with a message that is too long +TEST_P(ConfirmationUIAidlTest, MessageTooLongTest) { + static constexpr uint8_t test_extra[IConfirmationUI::MAX_MESSAGE_SIZE] = {}; + static constexpr char test_prompt[] = "D\'oh!"; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + sizeof(test_extra)); + auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}); + ASSERT_EQ(IConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG, getReturnCode(result)); +} + +// If the message gets very long some HAL implementations might fail even before the message +// reaches the trusted app implementation. But the HAL must still diagnose the correct error. +TEST_P(ConfirmationUIAidlTest, MessageWayTooLongTest) { + static constexpr uint8_t test_extra[(IConfirmationUI::MAX_MESSAGE_SIZE)*10] = {}; + static constexpr char test_prompt[] = "D\'oh!"; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + sizeof(test_extra)); + auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}); + ASSERT_EQ(IConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG, getReturnCode(result)); +} + +// Simulates the User tapping the Cancel +TEST_P(ConfirmationUIAidlTest, UserCancelTest) { + static constexpr char test_prompt[] = "Me first, gimme gimme!"; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk()); + + // Simulate the user taping ok. + ASSERT_TRUE(confirmator_->deliverSecureInputEvent(makeTestToken(TestModeCommands::CANCEL_EVENT)) + .isOk()); + // Wait for the callback. + EXPECT_EQ(std::cv_status::no_timeout, wait()); + ASSERT_EQ(IConfirmationUI::CANCELED, conf_cb->error_); + + ASSERT_EQ(0U, conf_cb->confirmationToken_.size()); + ASSERT_EQ(0U, conf_cb->formattedMessage_.size()); +} + +// Simulates the framework cancelling an ongoing prompt +TEST_P(ConfirmationUIAidlTest, AbortTest) { + static constexpr char test_prompt[] = "Me first, gimme gimme!"; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk()); + + confirmator_->abort(); + + // Wait for the callback. + EXPECT_EQ(std::cv_status::no_timeout, wait()); + ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_); + ASSERT_EQ(0U, conf_cb->confirmationToken_.size()); + ASSERT_EQ(0U, conf_cb->formattedMessage_.size()); +} + +// Tests if the confirmation dialog can successfully render 100 'W' characters as required by +// the design guidelines. +TEST_P(ConfirmationUIAidlTest, PortableMessageTest1) { + static constexpr char test_prompt[] = + "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW" + "WWWWWWWWWWWWWW"; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk()); + + confirmator_->abort(); + + // Wait for the callback. + EXPECT_EQ(std::cv_status::no_timeout, wait()); + ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_); + ASSERT_EQ(0U, conf_cb->confirmationToken_.size()); + ASSERT_EQ(0U, conf_cb->formattedMessage_.size()); +} + +// Tests if the confirmation dialog can successfully render 100 'W' characters as required by +// the design guidelines in magnified mode. +TEST_P(ConfirmationUIAidlTest, PortableMessageTest1Magnified) { + static constexpr char test_prompt[] = + "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW" + "WWWWWWWWWWWWWW"; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + ASSERT_TRUE(confirmator_ + ->promptUserConfirmation(conf_cb, prompt_text, extra, "en", + {UIOption::ACCESSIBILITY_MAGNIFIED}) + .isOk()); + + confirmator_->abort(); + + // Wait for the callback. + EXPECT_EQ(std::cv_status::no_timeout, wait()); + ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_); + ASSERT_EQ(0U, conf_cb->confirmationToken_.size()); + ASSERT_EQ(0U, conf_cb->formattedMessage_.size()); +} + +// Tests if the confirmation dialog can successfully render 8 groups of 12 'W' characters as +// required by the design guidelines. +TEST_P(ConfirmationUIAidlTest, PortableMessageTest2) { + static constexpr char test_prompt[] = + "WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW " + "WWWWWWWWWWWW WWWWWWWWWWWW"; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk()); + + confirmator_->abort(); + + // Wait for the callback. + EXPECT_EQ(std::cv_status::no_timeout, wait()); + ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_); + ASSERT_EQ(0U, conf_cb->confirmationToken_.size()); + ASSERT_EQ(0U, conf_cb->formattedMessage_.size()); +} + +// Tests if the confirmation dialog can successfully render 8 groups of 12 'W' characters as +// required by the design guidelines in magnified mode. +TEST_P(ConfirmationUIAidlTest, PortableMessageTest2Magnified) { + static constexpr char test_prompt[] = + "WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW " + "WWWWWWWWWWWW WWWWWWWWWWWW"; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + ASSERT_TRUE(confirmator_ + ->promptUserConfirmation(conf_cb, prompt_text, extra, "en", + {UIOption::ACCESSIBILITY_MAGNIFIED}) + .isOk()); + + confirmator_->abort(); + + // Wait for the callback. + EXPECT_EQ(std::cv_status::no_timeout, wait()); + ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_); + ASSERT_EQ(0U, conf_cb->confirmationToken_.size()); + ASSERT_EQ(0U, conf_cb->formattedMessage_.size()); +} + +// Passing malformed UTF-8 to the confirmation UI +// This test passes a string that ends in the middle of a multibyte character +TEST_P(ConfirmationUIAidlTest, MalformedUTF8Test1) { + static constexpr char test_prompt[] = {char(0xc0), 0}; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}); + ASSERT_EQ(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING, getReturnCode(result)); +} + +// Passing malformed UTF-8 to the confirmation UI +// This test passes a string with a 5-byte character. +TEST_P(ConfirmationUIAidlTest, MalformedUTF8Test2) { + static constexpr char test_prompt[] = {char(0xf8), char(0x82), char(0x82), + char(0x82), char(0x82), 0}; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}); + ASSERT_EQ(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING, getReturnCode(result)); +} + +// Passing malformed UTF-8 to the confirmation UI +// This test passes a string with a 2-byte character followed by a stray non UTF-8 character. +TEST_P(ConfirmationUIAidlTest, MalformedUTF8Test3) { + static constexpr char test_prompt[] = {char(0xc0), char(0x82), char(0x83), 0}; + static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3}; + shared_ptr conf_cb = + ::ndk::SharedRefBase::make(*this); + vector prompt_text(test_prompt, test_prompt + sizeof(test_prompt)); + vector extra(test_extra, test_extra + 3); + auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}); + ASSERT_EQ(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING, getReturnCode(result)); +} + +// Test the implementation of HMAC SHA 256 against a golden blob. +TEST(ConfirmationUITestSelfTest, HMAC256SelfTest) { + const char key_str[32] = "keykeykeykeykeykeykeykeykeykeyk"; + const uint8_t(&key)[32] = *reinterpret_cast(key_str); + auto expected = hex2str("2377fbcaa7fb3f6c20cfa1d9ebc60e9922cf58c909e25e300f3cb57f7805c886"); + auto result = HMacer::hmac256(key, "value1", "value2", "value3"); + +#ifdef DEBUG_CONFRIMATIONUI_UTILS_TEST + hexdump(std::cout, reinterpret_cast(expected.data()), 32) << std::endl; + hexdump(std::cout, result.value().data(), 32) << std::endl; +#endif + + ByteBufferProxy expected_bytes(expected); + ASSERT_TRUE(result.isOk()); + ASSERT_EQ(expected, result.value()); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ConfirmationUIAidlTest); +INSTANTIATE_TEST_SUITE_P( + PerInstance, ConfirmationUIAidlTest, + testing::ValuesIn(::android::getAidlHalInstanceNames(IConfirmationUI::descriptor)), + ::android::PrintInstanceNameToString); + +} // namespace aidl::android::hardware::confirmationui::test + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ABinderProcess_setThreadPoolMaxThreadCount(1); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +}