Keystore 2.0: Implement APC service.
This patch implements the Android Protected Confirmation service in
Keystore 2.0. This includes a C++ wrapper around the HIDL confirmationui
interface which will stay a HIDL interface for now.
This patch also includes the new AIDL specification.
This patch lacks death listener registration b/176491050.
Bug: 159341464
Bug: 173546269
Test: None
Change-Id: Ida4af108e86b538ab64d1dea4809cfa3b36f74cd
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 250646a..883271d 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -18,10 +18,12 @@
srcs: ["src/lib.rs"],
rustlibs: [
- "android.system.keystore2-rust",
"android.hardware.security.keymint-rust",
+ "android.security.apc-rust",
+ "android.system.keystore2-rust",
"libanyhow",
"libbinder_rs",
+ "libkeystore2_apc_compat-rust",
"libkeystore2_crypto_rust",
"libkeystore2_selinux",
"liblazy_static",
@@ -42,11 +44,13 @@
auto_gen_config: false,
test_config: "AndroidTest.xml",
rustlibs: [
- "android.system.keystore2-rust",
"android.hardware.security.keymint-rust",
+ "android.security.apc-rust",
+ "android.system.keystore2-rust",
"libandroid_logger",
"libanyhow",
"libbinder_rs",
+ "libkeystore2_apc_compat-rust",
"libkeystore2_crypto_rust",
"libkeystore2_selinux",
"liblazy_static",
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index 00be2b7..3051173 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -14,9 +14,7 @@
aidl_interface {
name: "android.security.attestationmanager",
- srcs: [
- "android/security/attestationmanager/*.aidl",
- ],
+ srcs: [ "android/security/attestationmanager/*.aidl", ],
imports: [ "android.hardware.security.keymint" ],
unstable: true,
backend: {
@@ -47,3 +45,16 @@
},
}
+aidl_interface {
+ name: "android.security.apc",
+ srcs: [ "android/security/apc/*.aidl" ],
+ unstable: true,
+ backend: {
+ java: {
+ enabled: true,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl b/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl
new file mode 100644
index 0000000..f47d7f5
--- /dev/null
+++ b/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.apc;
+
+import android.security.apc.ResponseCode;
+
+/**
+ * This callback interface must be implemented by the client to receive the result of the user
+ * confirmation.
+ */
+interface IConfirmationCallback {
+ /**
+ * This callback gets called by the implementing service when a pending confirmation prompt
+ * gets finalized.
+ *
+ * @param result
+ * - ResponseCode.OK On success. In this case dataConfirmed must be non null.
+ * - ResponseCode.CANCELLED If the user cancelled the prompt. In this case dataConfirmed must
+ * be null.
+ * - ResponseCode.ABORTED If the client called IProtectedConfirmation.cancelPrompt() or if the
+ * prompt was cancelled by the system due to an asynchronous event. In this case
+ * dataConfirmed must be null.
+ *
+ * @param dataConfirmed This is the message that was confirmed and for which a confirmation
+ * token is now available in implementing service. A subsequent attempt to sign this
+ * message with a confirmation bound key will succeed. The message is a CBOR map
+ * including the prompt text and the extra data.
+ */
+ oneway void onCompleted(in ResponseCode result, in @nullable byte[] dataConfirmed);
+}
diff --git a/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl b/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl
new file mode 100644
index 0000000..26ccf0f
--- /dev/null
+++ b/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl
@@ -0,0 +1,70 @@
+/*
+ * 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.apc;
+
+import android.security.apc.IConfirmationCallback;
+
+interface IProtectedConfirmation {
+
+ /**
+ * When set in the uiOptionFlags parameter of presentPrompt, indicates to the implementation
+ * that it shall use inverted color mode.
+ */
+ const int FLAG_UI_OPTION_INVERTED = 1;
+ /**
+ * When set in the uiOptionFlags parameter of presentPrompt, indicates to the implementation
+ * that it shall use magnified font mode.
+ */
+ const int FLAG_UI_OPTION_MAGNIFIED = 2;
+
+ /**
+ * Present the confirmation prompt. The caller must implement IConfirmationCallback and pass
+ * it to this function as listener.
+ *
+ * @param listener Must implement IConfirmationCallback. Doubles as session identifier when
+ * passed to cancelPrompt.
+ * @param promptText The text that will be displayed to the user using the protected
+ * confirmation UI.
+ * @param extraData Extra data, e.g., a nonce, that will be included in the to-be-signed
+ * message.
+ * @param locale The locale string is used to select the language for the instructions
+ * displayed by the confirmation prompt.
+ * @param uiOptionFlags Bitwise combination of FLAG_UI_OPTION_* see above.
+ *
+ * Service specific error codes:
+ * - ResponseCode.OPERATION_PENDING If another prompt is already pending.
+ * - ResponseCode.SYSTEM_ERROR An unexpected error occurred.
+ */
+ void presentPrompt(in IConfirmationCallback listener, in String promptText,
+ in byte[] extraData, in String locale, in int uiOptionFlags);
+
+ /**
+ * Cancel an ongoing prompt.
+ *
+ * @param listener Must implement IConfirmationCallback, although in this context this binder
+ * token is only used to identify the session that is to be cancelled.
+ *
+ * Service specific error code:
+ * - ResponseCode.IGNORED If the listener does not represent an ongoing prompt session.
+ */
+ void cancelPrompt(IConfirmationCallback listener);
+
+ /**
+ * Returns true if the device supports Android Protected Confirmation.
+ */
+ boolean isSupported();
+}
diff --git a/keystore2/aidl/android/security/apc/ResponseCode.aidl b/keystore2/aidl/android/security/apc/ResponseCode.aidl
new file mode 100644
index 0000000..7ae3e1c
--- /dev/null
+++ b/keystore2/aidl/android/security/apc/ResponseCode.aidl
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.apc;
+
+/**
+ * Used as service specific exception code by IProtectedConfirmation and as result
+ * code by IConfirmationCallback
+ */
+@Backing(type="int")
+enum ResponseCode {
+ /**
+ * The prompt completed successfully with the user confirming the message (callback result).
+ */
+ OK = 0,
+ /**
+ * The user cancelled the TUI (callback result).
+ */
+ CANCELLED = 1,
+ /**
+ * The prompt was aborted (callback result). This may happen when the app cancels the prompt,
+ * or when the prompt was cancelled due to an unexpected asynchronous event, such as an
+ * incoming phone call.
+ */
+ ABORTED = 2,
+ /**
+ * Another prompt cannot be started because another prompt is pending.
+ */
+ OPERATION_PENDING = 3,
+ /**
+ * The request was ignored.
+ */
+ IGNORED = 4,
+ /**
+ * An unexpected system error occurred.
+ */
+ SYSTEM_ERROR = 5,
+ /**
+ * Backend is not implemented.
+ */
+ UNIMPLEMENTED = 6,
+ /**
+ * Permission Denied.
+ */
+ PERMISSION_DENIED = 30,
+}
diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp
new file mode 100644
index 0000000..405e9b8
--- /dev/null
+++ b/keystore2/apc_compat/Android.bp
@@ -0,0 +1,56 @@
+// Copyright 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.
+
+cc_library {
+ name: "libkeystore2_apc_compat",
+ srcs: [
+ "apc_compat.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.confirmationui@1.0",
+ "libbase",
+ "libhidlbase",
+ "libutils",
+ ],
+}
+
+rust_bindgen {
+ name: "libkeystore2_apc_compat_bindgen",
+ wrapper_src: "apc_compat.hpp",
+ crate_name: "keystore2_apc_compat_bindgen",
+ source_stem: "bindings",
+
+ bindgen_flags: [
+ "--whitelist-function=tryGetUserConfirmationService",
+ "--whitelist-function=promptUserConfirmation",
+ "--whitelist-function=abortUserConfirmation",
+ "--whitelist-function=closeUserConfirmationService",
+ "--whitelist-var=INVALID_SERVICE_HANDLE",
+ "--whitelist-var=APC_COMPAT_.*",
+ ],
+}
+
+rust_library {
+ name: "libkeystore2_apc_compat-rust",
+ crate_name: "keystore2_apc_compat",
+ srcs: [
+ "apc_compat.rs",
+ ],
+ rustlibs: [
+ "libkeystore2_apc_compat_bindgen",
+ ],
+ shared_libs: [
+ "libkeystore2_apc_compat",
+ ],
+}
diff --git a/keystore2/apc_compat/apc_compat.cpp b/keystore2/apc_compat/apc_compat.cpp
new file mode 100644
index 0000000..08a8e45
--- /dev/null
+++ b/keystore2/apc_compat/apc_compat.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+#include "apc_compat.hpp"
+#include <android-base/logging.h>
+#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+#include <hwbinder/IBinder.h>
+
+#include <memory>
+#include <string>
+#include <thread>
+#include <vector>
+
+#define LOG_TAG "keystore2_apc_compat"
+
+namespace keystore2 {
+
+using android::sp;
+using android::hardware::hidl_death_recipient;
+using android::hardware::hidl_vec;
+using android::hardware::Return;
+using android::hardware::Status;
+using android::hardware::confirmationui::V1_0::IConfirmationResultCallback;
+using android::hardware::confirmationui::V1_0::IConfirmationUI;
+using android::hardware::confirmationui::V1_0::ResponseCode;
+using android::hardware::confirmationui::V1_0::UIOption;
+
+static uint32_t responseCode2Compat(ResponseCode rc) {
+ switch (rc) {
+ case ResponseCode::OK:
+ return APC_COMPAT_ERROR_OK;
+ case ResponseCode::Canceled:
+ return APC_COMPAT_ERROR_CANCELLED;
+ case ResponseCode::Aborted:
+ return APC_COMPAT_ERROR_ABORTED;
+ case ResponseCode::OperationPending:
+ return APC_COMPAT_ERROR_OPERATION_PENDING;
+ case ResponseCode::Ignored:
+ return APC_COMPAT_ERROR_IGNORED;
+ case ResponseCode::SystemError:
+ case ResponseCode::Unimplemented:
+ case ResponseCode::Unexpected:
+ case ResponseCode::UIError:
+ case ResponseCode::UIErrorMissingGlyph:
+ case ResponseCode::UIErrorMessageTooLong:
+ case ResponseCode::UIErrorMalformedUTF8Encoding:
+ default:
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+}
+
+class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_death_recipient {
+ public:
+ static sp<ConfuiCompatSession>* tryGetService() {
+ sp<IConfirmationUI> service = IConfirmationUI::tryGetService();
+ if (service) {
+ return new sp(new ConfuiCompatSession(std::move(service)));
+ } else {
+ return nullptr;
+ }
+ }
+
+ uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text,
+ const uint8_t* extra_data, size_t extra_data_size,
+ const char* locale, ApcCompatUiOptions ui_options) {
+ std::string hidl_prompt(prompt_text);
+ std::vector<uint8_t> hidl_extra(extra_data, extra_data + extra_data_size);
+ std::string hidl_locale(locale);
+ std::vector<UIOption> hidl_ui_options;
+ if (ui_options.inverted) {
+ hidl_ui_options.push_back(UIOption::AccessibilityInverted);
+ }
+ if (ui_options.magnified) {
+ hidl_ui_options.push_back(UIOption::AccessibilityMagnified);
+ }
+ auto lock = std::lock_guard(callback_lock_);
+ if (callback_.result != nullptr) {
+ return APC_COMPAT_ERROR_OPERATION_PENDING;
+ }
+ auto err = service_->linkToDeath(sp(this), 0);
+ if (!err.isOk()) {
+ LOG(ERROR) << "Communication error: promptUserConfirmation: "
+ "Trying to register death recipient: "
+ << err.description();
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+
+ auto rc = service_->promptUserConfirmation(sp(this), hidl_prompt, hidl_extra, hidl_locale,
+ hidl_ui_options);
+ if (!rc.isOk()) {
+ LOG(ERROR) << "Communication error: promptUserConfirmation: " << rc.description();
+ }
+ if (rc == ResponseCode::OK) {
+ callback_ = callback;
+ }
+ return responseCode2Compat(rc.withDefault(ResponseCode::SystemError));
+ }
+
+ void abort() { service_->abort(); }
+
+ void
+ finalize(ResponseCode responseCode,
+ std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> dataConfirmed,
+ std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> confirmationToken) {
+ ApcCompatCallback callback;
+ {
+ auto lock = std::lock_guard(callback_lock_);
+ // Calling the callback consumes the callback data structure. We have to make
+ // sure that it can only be called once.
+ callback = callback_;
+ callback_ = {nullptr, nullptr};
+ // Unlock the callback_lock_ here. It must never be held while calling the callback.
+ }
+
+ if (callback.result != nullptr) {
+ service_->unlinkToDeath(sp(this));
+
+ size_t dataConfirmedSize = 0;
+ const uint8_t* dataConfirmedPtr = nullptr;
+ size_t confirmationTokenSize = 0;
+ const uint8_t* confirmationTokenPtr = nullptr;
+ if (responseCode == ResponseCode::OK) {
+ if (dataConfirmed) {
+ dataConfirmedPtr = dataConfirmed->get().data();
+ dataConfirmedSize = dataConfirmed->get().size();
+ }
+ if (dataConfirmed) {
+ confirmationTokenPtr = confirmationToken->get().data();
+ confirmationTokenSize = confirmationToken->get().size();
+ }
+ }
+ callback.result(callback.data, responseCode2Compat(responseCode), dataConfirmedPtr,
+ dataConfirmedSize, confirmationTokenPtr, confirmationTokenSize);
+ }
+ }
+
+ // IConfirmationResultCallback overrides:
+ android::hardware::Return<void> result(ResponseCode responseCode,
+ const hidl_vec<uint8_t>& dataConfirmed,
+ const hidl_vec<uint8_t>& confirmationToken) override {
+ finalize(responseCode, dataConfirmed, confirmationToken);
+ return Status::ok();
+ };
+
+ void serviceDied(uint64_t /* cookie */,
+ const ::android::wp<::android::hidl::base::V1_0::IBase>& /* who */) override {
+ finalize(ResponseCode::SystemError, {}, {});
+ }
+
+ private:
+ ConfuiCompatSession(sp<IConfirmationUI> service)
+ : service_(service), callback_{nullptr, nullptr} {}
+ sp<IConfirmationUI> service_;
+
+ // The callback_lock_ protects the callback_ field against concurrent modification.
+ // IMPORTANT: It must never be held while calling the call back.
+ std::mutex callback_lock_;
+ ApcCompatCallback callback_;
+};
+
+} // namespace keystore2
+
+using namespace keystore2;
+
+ApcCompatServiceHandle tryGetUserConfirmationService() {
+ return reinterpret_cast<ApcCompatServiceHandle>(ConfuiCompatSession::tryGetService());
+}
+
+uint32_t promptUserConfirmation(ApcCompatServiceHandle handle, ApcCompatCallback callback,
+ const char* prompt_text, const uint8_t* extra_data,
+ size_t extra_data_size, char const* locale,
+ ApcCompatUiOptions ui_options) {
+ auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
+ return (*session)->promptUserConfirmation(callback, prompt_text, extra_data, extra_data_size,
+ locale, ui_options);
+}
+
+void abortUserConfirmation(ApcCompatServiceHandle handle) {
+ auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
+ (*session)->abort();
+}
+
+void closeUserConfirmationService(ApcCompatServiceHandle handle) {
+ // Closing the handle implicitly aborts an ongoing sessions.
+ // Note that a resulting callback is still safely conducted, because we only delete a
+ // StrongPointer below. libhwbinder still owns another StrongPointer to this session.
+ abortUserConfirmation(handle);
+ delete reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
+}
+
+const ApcCompatServiceHandle INVALID_SERVICE_HANDLE = nullptr;
diff --git a/keystore2/apc_compat/apc_compat.hpp b/keystore2/apc_compat/apc_compat.hpp
new file mode 100644
index 0000000..15fa5f4
--- /dev/null
+++ b/keystore2/apc_compat/apc_compat.hpp
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+using ApcCompatServiceHandle = void*;
+
+#define APC_COMPAT_ERROR_OK 0
+#define APC_COMPAT_ERROR_CANCELLED 1
+#define APC_COMPAT_ERROR_ABORTED 2
+#define APC_COMPAT_ERROR_OPERATION_PENDING 3
+#define APC_COMPAT_ERROR_IGNORED 4
+#define APC_COMPAT_ERROR_SYSTEM_ERROR 5
+
+extern "C" {
+
+extern const ApcCompatServiceHandle INVALID_SERVICE_HANDLE;
+
+/**
+ * This struct holds the ui options for the protected confirmation dialog.
+ */
+struct ApcCompatUiOptions {
+ /**
+ * If set to true inverted color mode is used.
+ */
+ bool inverted;
+ /**
+ * If set to true magnified fonts are used.
+ */
+ bool magnified;
+};
+
+/**
+ * Represents a result callback that is called when a confirmation session was successfully
+ * started.
+ * The field `data` is an opaque callback context handle. It must be passed to the `result`
+ * function.
+ *
+ * IMPORTANT: The life cycle of `data` ends when `result` is called. The callback must not
+ * be called a second time.
+ *
+ * The callback function `result` has the prototype:
+ * void result(
+ * void* data,
+ * uint32_t rc,
+ * const uint8_t* tbs_message,
+ * size_t tbs_message_size,
+ * const uint8_t* confirmation_token,
+ * size_t confirmation_token_size)
+ *
+ * * data - must be the data field of the structure.
+ * * rc - response code, one of:
+ * * APC_COMPAT_ERROR_OK - The user confirmed the prompt text.
+ * * APC_COMPAT_ERROR_CANCELLED - The user rejected the prompt text.
+ * * APC_COMPAT_ERROR_ABORTED - `abortUserConfirmation` was called.
+ * * APC_COMPAT_ERROR_SYSTEM_ERROR - An unspecified system error occurred.
+ * * tbs_message(_size) - Pointer to and size of the to-be-signed message. Must
+ * be NULL and 0 respectively if `rc != APC_COMPAT_ERROR_OK`.
+ * * confirmation_token(_size) - Pointer to and size of the confirmation token. Must
+ * be NULL and 0 respectively if `rc != APC_COMPAT_ERROR_OK`.
+ */
+struct ApcCompatCallback {
+ void* data;
+ void (*result)(void*, uint32_t, const uint8_t*, size_t, const uint8_t*, size_t);
+};
+
+/**
+ * Attempts to make a connection to the confirmationui HIDL backend.
+ * If a valid service handle is returned it stays valid until
+ * `closeUserConfirmationService` is called.
+ *
+ * @return A valid service handle on success or INVALID_SERVICE_HANDLE
+ * on failure.
+ */
+ApcCompatServiceHandle tryGetUserConfirmationService();
+
+/**
+ * Attempts to start a protected confirmation session on the given service handle.
+ * The function takes ownership of the callback object (`cb`) IFF APC_COMPAT_ERROR_OK
+ * is returned. The resources referenced by the callback object must stay valid
+ * until the callback is called.
+ *
+ * @param handle A valid service handle as returned by `tryGetUserConfirmationService()`.
+ * @cb A ApcCompatCallback structure that represents a callback function with session data.
+ * @param prompt_text A UTF-8 encoded prompt string.
+ * @param extra_data Free form extra data.
+ * @param extra_data_size size of the extra data buffer in bytes.
+ * @param locale A locale string.
+ * @param ui_options A UI options. See ApcCompatUiOptions above.
+ * @retval APC_COMPAT_ERROR_OK on success.
+ * @retval APC_COMPAT_ERROR_OPERATION_PENDING if another operation was already in progress.
+ * @retval APC_COMPAT_ERROR_SYSTEM_ERROR if an unspecified system error occurred.
+ */
+uint32_t promptUserConfirmation(ApcCompatServiceHandle handle, struct ApcCompatCallback cb,
+ const char* prompt_text, const uint8_t* extra_data,
+ size_t extra_data_size, char const* locale,
+ ApcCompatUiOptions ui_options);
+
+/**
+ * Aborts a running confirmation session or no-op if no session is running.
+ * If a session is running this results in a `result` callback with
+ * `rc == APC_COMPAT_ERROR_ABORTED`. Mind though that the callback can still yield other
+ * results even after this function was called, because it may race with an actual user
+ * response. In any case, there will be only one callback response for each session
+ * successfully started with promptUserConfirmation.
+ *
+ * @param handle A valid session handle as returned by `tryGetUserConfirmationService()`
+ */
+void abortUserConfirmation(ApcCompatServiceHandle handle);
+
+/**
+ * Closes a valid service session as returned by `tryGetUserConfirmationService()`.
+ * If a session is still running it is implicitly aborted. In this case, freeing up of the resources
+ * referenced by the service handle is deferred until the callback has completed.
+ *
+ * @param handle A valid session handle as returned by `tryGetUserConfirmationService()`
+ */
+void closeUserConfirmationService(ApcCompatServiceHandle);
+
+}
diff --git a/keystore2/apc_compat/apc_compat.rs b/keystore2/apc_compat/apc_compat.rs
new file mode 100644
index 0000000..4391f5b
--- /dev/null
+++ b/keystore2/apc_compat/apc_compat.rs
@@ -0,0 +1,201 @@
+// Copyright 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.
+
+//! This crate implements a safe wrapper around the ConfirmationUI HIDL spec, which
+//! is the backend for Android Protected Confirmation (APC).
+//!
+//! It provides a safe wrapper around a C++ implementation of ConfirmationUI
+//! client.
+
+use keystore2_apc_compat_bindgen::{
+ abortUserConfirmation, closeUserConfirmationService, promptUserConfirmation, size_t,
+ tryGetUserConfirmationService, ApcCompatCallback, ApcCompatServiceHandle,
+};
+pub use keystore2_apc_compat_bindgen::{
+ ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
+ APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
+ APC_COMPAT_ERROR_SYSTEM_ERROR, INVALID_SERVICE_HANDLE,
+};
+use std::{ffi::CString, slice};
+
+/// Safe wrapper around the ConfirmationUI HIDL spec.
+///
+/// # Example
+/// ```
+/// struct Cb();
+/// impl ApcHalCallback for Cb {
+/// fn result(
+/// &self,
+/// rc: u32,
+/// message: Option<&[u8]>,
+/// token: Option<&[u8]>,
+/// ) {
+/// println!("Callback called with rc: {}, message: {}, token: {}", rc, message, token);
+/// }
+/// };
+///
+/// fn prompt() -> Result<(), u32> {
+/// let hal = ApcHal::try_get_service()?;
+/// hal.prompt_user_confirmation(Box::new(Cb()), "Do you agree?", b"extra data", "en", 0)?;
+/// }
+///
+/// ```
+pub struct ApcHal(ApcCompatServiceHandle);
+
+unsafe impl Send for ApcHal {}
+unsafe impl Sync for ApcHal {}
+
+impl Drop for ApcHal {
+ fn drop(&mut self) {
+ // # Safety:
+ // This ends the life cycle of the contained `ApcCompatServiceHandle` owned by this
+ // `ApcHal` object.
+ //
+ // `ApcHal` objects are only created if a valid handle was acquired so self.0 is
+ // always valid when dropped.
+ unsafe {
+ closeUserConfirmationService(self.0);
+ }
+ }
+}
+
+type Callback = dyn FnOnce(u32, Option<&[u8]>, Option<&[u8]>);
+
+extern "C" fn confirmation_result_callback(
+ handle: *mut ::std::os::raw::c_void,
+ rc: u32,
+ tbs_message: *const u8,
+ tbs_message_size: size_t,
+ confirmation_token: *const u8,
+ confirmation_token_size: size_t,
+) {
+ // # Safety:
+ // The C/C++ implementation must pass to us the handle that was created
+ // and assigned to the `ApcCompatCallback::data` field in
+ // `ApcHal::prompt_user_confirmation` below. Also we consume the handle,
+ // by letting `hal_cb` go out of scope with this function call. So
+ // the C/C++ implementation must assure that each `ApcCompatCallback` is only used once.
+ let hal_cb: Box<Box<Callback>> = unsafe { Box::from_raw(handle as *mut Box<Callback>) };
+ let tbs_message = match (tbs_message.is_null(), tbs_message_size) {
+ (true, _) | (_, 0) => None,
+ (false, s) => Some(
+ // # Safety:
+ // If the pointer and size is not nullptr and not 0 respectively, the C/C++
+ // implementation must pass a valid pointer to an allocation of at least size bytes,
+ // and the pointer must be valid until this function returns.
+ unsafe { slice::from_raw_parts(tbs_message, s as usize) },
+ ),
+ };
+ let confirmation_token = match (confirmation_token.is_null(), confirmation_token_size) {
+ (true, _) | (_, 0) => None,
+ (false, s) => Some(
+ // # Safety:
+ // If the pointer and size is not nullptr and not 0 respectively, the C/C++
+ // implementation must pass a valid pointer to an allocation of at least size bytes,
+ // and the pointer must be valid until this function returns.
+ unsafe { slice::from_raw_parts(confirmation_token, s as usize) },
+ ),
+ };
+ hal_cb(rc, tbs_message, confirmation_token)
+}
+
+impl ApcHal {
+ /// Attempts to connect to the APC (confirmationui) backend. On success, it returns an
+ /// initialized `ApcHal` object.
+ pub fn try_get_service() -> Option<Self> {
+ // # Safety:
+ // `tryGetUserConfirmationService` returns a valid handle or INVALID_SERVICE_HANDLE.
+ // On success, `ApcHal` takes ownership of this handle and frees it with
+ // `closeUserConfirmationService` when dropped.
+ let handle = unsafe { tryGetUserConfirmationService() };
+ match handle {
+ h if h == unsafe { INVALID_SERVICE_HANDLE } => None,
+ h => Some(Self(h)),
+ }
+ }
+
+ /// Attempts to start a confirmation prompt. The given callback is consumed, and it is
+ /// guaranteed to be called eventually IFF this function returns `APC_COMPAT_ERROR_OK`.
+ ///
+ /// The callback has the following arguments:
+ /// rc: u32 - The reason for the termination which takes one of the values.
+ /// * `APC_COMPAT_ERROR_OK` - The user confirmed the prompted message.
+ /// * `APC_COMPAT_ERROR_CANCELLED` - The user rejected the prompted message.
+ /// * `APC_COMPAT_ERROR_ABORTED` - The prompt was aborted either because the client
+ /// aborted. the session or an asynchronous system event occurred that ended the
+ /// prompt prematurely.
+ /// * `APC_COMPAT_ERROR_SYSTEMERROR` - An unspecified system error occurred. Logs may
+ /// have more information.
+ ///
+ /// data_confirmed: Option<&[u8]> and
+ /// confirmation_token: Option<&[u8]> hold the confirmed message and the confirmation token
+ /// respectively. They must be `Some()` if `rc == APC_COMPAT_ERROR_OK` and `None` otherwise.
+ pub fn prompt_user_confirmation<F>(
+ &self,
+ prompt_text: &str,
+ extra_data: &[u8],
+ locale: &str,
+ ui_opts: ApcCompatUiOptions,
+ cb: F,
+ ) -> Result<(), u32>
+ where
+ F: FnOnce(u32, Option<&[u8]>, Option<&[u8]>),
+ {
+ let cb_data_ptr = Box::into_raw(Box::new(Box::new(cb)));
+ let cb = ApcCompatCallback {
+ data: cb_data_ptr as *mut std::ffi::c_void,
+ result: Some(confirmation_result_callback),
+ };
+ let prompt_text = CString::new(prompt_text).unwrap();
+ let locale = CString::new(locale).unwrap();
+ // # Safety:
+ // The `ApcCompatCallback` object (`cb`) is passed to the callee by value, and with it
+ // ownership of the `data` field pointer. The data pointer is guaranteed to be valid
+ // until the C/C++ implementation calls the callback. Calling the callback consumes
+ // the data pointer. The C/C++ implementation must not access it after calling the
+ // callback and it must not call the callback a second time.
+ //
+ // The C/C++ must make no assumptions about the life time of the other parameters after
+ // the function returns.
+ let rc = unsafe {
+ promptUserConfirmation(
+ self.0,
+ cb,
+ prompt_text.as_ptr(),
+ extra_data.as_ptr(),
+ extra_data.len() as size_t,
+ locale.as_ptr(),
+ ui_opts,
+ )
+ };
+ match rc {
+ APC_COMPAT_ERROR_OK => Ok(()),
+ rc => {
+ // # Safety:
+ // If promptUserConfirmation does not succeed, it must not take ownership of the
+ // callback, so we must destroy it.
+ unsafe { Box::from_raw(cb_data_ptr) };
+ Err(rc)
+ }
+ }
+ }
+
+ /// Aborts a running confirmation session, or no-op if none is running.
+ pub fn abort(&self) {
+ // # Safety:
+ // It is always safe to call `abortUserConfirmation`, because spurious calls are ignored.
+ // The handle argument must be valid, but this is an invariant of `ApcHal`.
+ unsafe { abortUserConfirmation(self.0) }
+ }
+}
diff --git a/keystore2/src/apc.rs b/keystore2/src/apc.rs
new file mode 100644
index 0000000..105e071
--- /dev/null
+++ b/keystore2/src/apc.rs
@@ -0,0 +1,366 @@
+// Copyright 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.
+
+// TODO The confirmation token is yet unused.
+#![allow(unused_variables)]
+
+//! This module implements the Android Protected Confirmation (APC) service as defined
+//! in the android.security.apc AIDL spec.
+
+use std::{
+ cmp::PartialEq,
+ collections::HashMap,
+ sync::{Arc, Mutex},
+};
+
+use crate::utils::{compat_2_response_code, ui_opts_2_compat};
+use android_security_apc::aidl::android::security::apc::{
+ IConfirmationCallback::IConfirmationCallback,
+ IProtectedConfirmation::{BnProtectedConfirmation, IProtectedConfirmation},
+ ResponseCode::ResponseCode,
+};
+use android_security_apc::binder::{
+ ExceptionCode, Interface, Result as BinderResult, SpIBinder, Status as BinderStatus,
+};
+use anyhow::{Context, Result};
+use binder::{IBinder, ThreadState};
+use keystore2_apc_compat::ApcHal;
+use keystore2_selinux as selinux;
+use std::time::{Duration, Instant};
+
+/// This is the main APC error type, it wraps binder exceptions and the
+/// APC ResponseCode.
+#[derive(Debug, thiserror::Error, PartialEq)]
+pub enum Error {
+ /// Wraps an Android Protected Confirmation (APC) response code as defined by the
+ /// android.security.apc AIDL interface specification.
+ #[error("Error::Rc({0:?})")]
+ Rc(ResponseCode),
+ /// Wraps a Binder exception code other than a service specific exception.
+ #[error("Binder exception code {0:?}, {1:?}")]
+ Binder(ExceptionCode, i32),
+}
+
+impl Error {
+ /// Short hand for `Error::Rc(ResponseCode::SYSTEM_ERROR)`
+ pub fn sys() -> Self {
+ Error::Rc(ResponseCode::SYSTEM_ERROR)
+ }
+
+ /// Short hand for `Error::Rc(ResponseCode::OPERATION_PENDING)`
+ pub fn pending() -> Self {
+ Error::Rc(ResponseCode::OPERATION_PENDING)
+ }
+
+ /// Short hand for `Error::Rc(ResponseCode::CANCELLED)`
+ pub fn cancelled() -> Self {
+ Error::Rc(ResponseCode::CANCELLED)
+ }
+
+ /// Short hand for `Error::Rc(ResponseCode::ABORTED)`
+ pub fn aborted() -> Self {
+ Error::Rc(ResponseCode::ABORTED)
+ }
+
+ /// Short hand for `Error::Rc(ResponseCode::IGNORED)`
+ pub fn ignored() -> Self {
+ Error::Rc(ResponseCode::IGNORED)
+ }
+
+ /// Short hand for `Error::Rc(ResponseCode::UNIMPLEMENTED)`
+ pub fn unimplemented() -> Self {
+ Error::Rc(ResponseCode::UNIMPLEMENTED)
+ }
+}
+
+/// This function should be used by confirmation service calls to translate error conditions
+/// into service specific exceptions.
+///
+/// All error conditions get logged by this function.
+///
+/// `Error::Rc(x)` variants get mapped onto a service specific error code of `x`.
+/// `selinux::Error::perm()` is mapped on `ResponseCode::PERMISSION_DENIED`.
+///
+/// All non `Error` error conditions get mapped onto ResponseCode::SYSTEM_ERROR`.
+///
+/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
+/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
+/// typically returns Ok(value).
+pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T>
+where
+ F: FnOnce(U) -> BinderResult<T>,
+{
+ result.map_or_else(
+ |e| {
+ log::error!("{:#?}", e);
+ let root_cause = e.root_cause();
+ let rc = match root_cause.downcast_ref::<Error>() {
+ Some(Error::Rc(rcode)) => rcode.0,
+ Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0,
+ None => match root_cause.downcast_ref::<selinux::Error>() {
+ Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0,
+ _ => ResponseCode::SYSTEM_ERROR.0,
+ },
+ };
+ Err(BinderStatus::new_service_specific_error(rc, None))
+ },
+ handle_ok,
+ )
+}
+
+/// Rate info records how many failed attempts a client has made to display a protected
+/// confirmation prompt. Clients are penalized for attempts that get declined by the user
+/// or attempts that get aborted by the client itself.
+///
+/// After the third failed attempt the client has to cool down for 30 seconds before it
+/// it can retry. After the sixth failed attempt, the time doubles with every failed attempt
+/// until it goes into saturation at 24h.
+///
+/// A successful user prompt resets the counter.
+#[derive(Debug, Clone)]
+struct RateInfo {
+ counter: u32,
+ timestamp: Instant,
+}
+
+impl RateInfo {
+ const ONE_DAY: Duration = Duration::from_secs(60u64 * 60u64 * 24u64);
+
+ fn get_remaining_back_off(&self) -> Option<Duration> {
+ let back_off = match self.counter {
+ // The first three attempts come without penalty.
+ 0..=2 => return None,
+ // The next three attempts are are penalized with 30 seconds back off time.
+ 3..=5 => Duration::from_secs(30),
+ // After that we double the back off time the with every additional attempt
+ // until we reach 1024m (~17h).
+ 6..=16 => Duration::from_secs(60)
+ .checked_mul(1u32 << (self.counter - 6))
+ .unwrap_or(Self::ONE_DAY),
+ // After that we cap of at 24h between attempts.
+ _ => Self::ONE_DAY,
+ };
+ let elapsed = self.timestamp.elapsed();
+ // This does exactly what we want.
+ // `back_off - elapsed` is the remaining back off duration or None if elapsed is larger
+ // than back_off. Also, this operation cannot overflow as long as elapsed is less than
+ // back_off, which is all that we care about.
+ back_off.checked_sub(elapsed)
+ }
+}
+
+impl Default for RateInfo {
+ fn default() -> Self {
+ Self { counter: 0u32, timestamp: Instant::now() }
+ }
+}
+
+/// The APC session state represents the state of an APC session.
+struct ApcSessionState {
+ /// A reference to the APC HAL backend.
+ hal: Arc<ApcHal>,
+ /// The client callback object.
+ cb: SpIBinder,
+ /// The uid of the owner of this APC session.
+ uid: u32,
+ /// The time when this session was started.
+ start: Instant,
+ /// This is set when the client calls abort.
+ /// This is used by the rate limiting logic to determine
+ /// if the client needs to be penalized for this attempt.
+ client_aborted: bool,
+}
+
+#[derive(Default)]
+struct ApcState {
+ session: Option<ApcSessionState>,
+ rate_limiting: HashMap<u32, RateInfo>,
+}
+
+/// Implementation of the APC service.
+pub struct ApcManager {
+ state: Arc<Mutex<ApcState>>,
+}
+
+impl Interface for ApcManager {}
+
+impl ApcManager {
+ /// Create a new instance of the Android Protected Confirmation service.
+ pub fn new_native_binder() -> Result<impl IProtectedConfirmation> {
+ let result = BnProtectedConfirmation::new_binder(Self {
+ state: Arc::new(Mutex::new(Default::default())),
+ });
+ result.as_binder().set_requesting_sid(true);
+ Ok(result)
+ }
+
+ fn result(
+ state: Arc<Mutex<ApcState>>,
+ rc: u32,
+ data_confirmed: Option<&[u8]>,
+ confirmation_token: Option<&[u8]>,
+ ) {
+ let mut state = state.lock().unwrap();
+ let (callback, uid, start, client_aborted) = match state.session.take() {
+ None => return, // Nothing to do
+ Some(ApcSessionState { cb: callback, uid, start, client_aborted, .. }) => {
+ (callback, uid, start, client_aborted)
+ }
+ };
+
+ let rc = compat_2_response_code(rc);
+
+ // Update rate limiting information.
+ match (rc, client_aborted) {
+ // If the user confirmed the dialog.
+ (ResponseCode::OK, _) => {
+ // Reset counter.
+ state.rate_limiting.remove(&uid);
+ // TODO at this point we need to send the confirmation token to where keystore can
+ // use it.
+ }
+ // If cancelled by the user or if aborted by the client.
+ (ResponseCode::CANCELLED, _) | (ResponseCode::ABORTED, true) => {
+ // Penalize.
+ let mut rate_info = state.rate_limiting.entry(uid).or_default();
+ rate_info.counter += 1;
+ rate_info.timestamp = start;
+ }
+ // In any other case this try does not count at all.
+ _ => {}
+ }
+ drop(state);
+
+ if let Ok(listener) = callback.into_interface::<dyn IConfirmationCallback>() {
+ if let Err(e) = listener.onCompleted(rc, data_confirmed) {
+ log::error!(
+ "In ApcManagerCallback::result: Reporting completion to client failed {:?}",
+ e
+ )
+ }
+ } else {
+ log::error!("In ApcManagerCallback::result: SpIBinder is not a IConfirmationCallback.");
+ }
+ }
+
+ fn present_prompt(
+ &self,
+ listener: &dyn IConfirmationCallback,
+ prompt_text: &str,
+ extra_data: &[u8],
+ locale: &str,
+ ui_option_flags: i32,
+ ) -> Result<()> {
+ let mut state = self.state.lock().unwrap();
+ if state.session.is_some() {
+ return Err(Error::pending())
+ .context("In ApcManager::present_prompt: Session pending.");
+ }
+
+ // Perform rate limiting.
+ let uid = ThreadState::get_calling_uid();
+ match state.rate_limiting.get(&uid) {
+ None => {}
+ Some(rate_info) => {
+ if let Some(back_off) = rate_info.get_remaining_back_off() {
+ return Err(Error::sys()).context(format!(
+ "In ApcManager::present_prompt: Cooling down. Remaining back-off: {}s",
+ back_off.as_secs()
+ ));
+ }
+ }
+ }
+
+ let hal = ApcHal::try_get_service();
+ let hal = match hal {
+ None => {
+ return Err(Error::unimplemented())
+ .context("In ApcManager::present_prompt: APC not supported.")
+ }
+ Some(h) => Arc::new(h),
+ };
+
+ let ui_opts = ui_opts_2_compat(ui_option_flags);
+
+ let state_clone = self.state.clone();
+ hal.prompt_user_confirmation(
+ prompt_text,
+ extra_data,
+ locale,
+ ui_opts,
+ |rc, data_confirmed, confirmation_token| {
+ Self::result(state_clone, rc, data_confirmed, confirmation_token)
+ },
+ )
+ .map_err(|rc| Error::Rc(compat_2_response_code(rc)))
+ .context("In present_prompt: Failed to present prompt.")?;
+ state.session = Some(ApcSessionState {
+ hal,
+ cb: listener.as_binder(),
+ uid,
+ start: Instant::now(),
+ client_aborted: false,
+ });
+ Ok(())
+ }
+
+ fn cancel_prompt(&self, listener: &dyn IConfirmationCallback) -> Result<()> {
+ let mut state = self.state.lock().unwrap();
+ let hal = match &mut state.session {
+ None => {
+ return Err(Error::ignored())
+ .context("In cancel_prompt: Attempt to cancel non existing session. Ignoring.")
+ }
+ Some(session) => {
+ if session.cb != listener.as_binder() {
+ return Err(Error::ignored()).context(concat!(
+ "In cancel_prompt: Attempt to cancel session not belonging to caller. ",
+ "Ignoring."
+ ));
+ }
+ session.client_aborted = true;
+ session.hal.clone()
+ }
+ };
+ drop(state);
+ hal.abort();
+ Ok(())
+ }
+
+ fn is_supported() -> Result<bool> {
+ Ok(ApcHal::try_get_service().is_some())
+ }
+}
+
+impl IProtectedConfirmation for ApcManager {
+ fn presentPrompt(
+ &self,
+ listener: &dyn IConfirmationCallback,
+ prompt_text: &str,
+ extra_data: &[u8],
+ locale: &str,
+ ui_option_flags: i32,
+ ) -> BinderResult<()> {
+ map_or_log_err(
+ self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags),
+ Ok,
+ )
+ }
+ fn cancelPrompt(&self, listener: &dyn IConfirmationCallback) -> BinderResult<()> {
+ map_or_log_err(self.cancel_prompt(listener), Ok)
+ }
+ fn isSupported(&self) -> BinderResult<bool> {
+ map_or_log_err(Self::is_supported(), Ok)
+ }
+}
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 2916549..59e5972 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -15,11 +15,13 @@
//! This crate implements the Keystore 2.0 service entry point.
use binder::Interface;
+use keystore2::apc::ApcManager;
use keystore2::service::KeystoreService;
use log::{error, info};
use std::panic;
static KS2_SERVICE_NAME: &str = "android.system.keystore2";
+static APC_SERVICE_NAME: &str = "android.security.apc";
/// Keystore 2.0 takes one argument which is a path indicating its designated working directory.
fn main() {
@@ -55,6 +57,13 @@
panic!("Failed to register service {} because of {:?}.", KS2_SERVICE_NAME, e);
});
+ let apc_service = ApcManager::new_native_binder().unwrap_or_else(|e| {
+ panic!("Failed to create service {} because of {:?}.", APC_SERVICE_NAME, e);
+ });
+ binder::add_service(APC_SERVICE_NAME, apc_service.as_binder()).unwrap_or_else(|e| {
+ panic!("Failed to register service {} because of {:?}.", APC_SERVICE_NAME, e);
+ });
+
info!("Successfully registered Keystore 2.0 service.");
info!("Starting thread pool now.");
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 5ad77d1..3fb938c 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -14,6 +14,7 @@
//! This crate implements the Android Keystore 2.0 service.
+pub mod apc;
pub mod auth_token_handler;
pub mod database;
pub mod enforcements;
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index d50f70e..eab9b4d 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -25,11 +25,20 @@
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
KeyCharacteristics::KeyCharacteristics, SecurityLevel::SecurityLevel,
};
+use android_security_apc::aidl::android::security::apc::{
+ IProtectedConfirmation::{FLAG_UI_OPTION_INVERTED, FLAG_UI_OPTION_MAGNIFIED},
+ ResponseCode::ResponseCode as ApcResponseCode,
+};
use android_system_keystore2::aidl::android::system::keystore2::{
Authorization::Authorization, KeyDescriptor::KeyDescriptor,
};
use anyhow::{anyhow, Context};
use binder::{FromIBinder, SpIBinder, ThreadState};
+use keystore2_apc_compat::{
+ ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
+ APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
+ APC_COMPAT_ERROR_SYSTEM_ERROR,
+};
use std::convert::TryFrom;
use std::sync::Mutex;
@@ -146,6 +155,31 @@
i64::try_from(current_time.tv_sec).unwrap()
}
+/// Converts a response code as returned by the Android Protected Confirmation HIDL compatibility
+/// module (keystore2_apc_compat) into a ResponseCode as defined by the APC AIDL
+/// (android.security.apc) spec.
+pub fn compat_2_response_code(rc: u32) -> ApcResponseCode {
+ match rc {
+ APC_COMPAT_ERROR_OK => ApcResponseCode::OK,
+ APC_COMPAT_ERROR_CANCELLED => ApcResponseCode::CANCELLED,
+ APC_COMPAT_ERROR_ABORTED => ApcResponseCode::ABORTED,
+ APC_COMPAT_ERROR_OPERATION_PENDING => ApcResponseCode::OPERATION_PENDING,
+ APC_COMPAT_ERROR_IGNORED => ApcResponseCode::IGNORED,
+ APC_COMPAT_ERROR_SYSTEM_ERROR => ApcResponseCode::SYSTEM_ERROR,
+ _ => ApcResponseCode::SYSTEM_ERROR,
+ }
+}
+
+/// Converts the UI Options flags as defined by the APC AIDL (android.security.apc) spec into
+/// UI Options flags as defined by the Android Protected Confirmation HIDL compatibility
+/// module (keystore2_apc_compat).
+pub fn ui_opts_2_compat(opt: i32) -> ApcCompatUiOptions {
+ ApcCompatUiOptions {
+ inverted: (opt & FLAG_UI_OPTION_INVERTED) != 0,
+ magnified: (opt & FLAG_UI_OPTION_MAGNIFIED) != 0,
+ }
+}
+
/// AID offset for uid space partitioning.
/// TODO: Replace with bindgen generated from libcutils. b/175619259
pub const AID_USER_OFFSET: u32 = 100000;