From 8fe0cfb098e6062c1837f4be30e8dde77a00c5d1 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Mon, 13 Jan 2020 14:24:32 -0800 Subject: [PATCH] First working version of the confirmationui HAL service This implementation does not provide any security guaranties. * The input method (NotSoSecureInput) runs a crypto protocols that is sufficiently secure IFF the end point is implemented on a trustworthy secure input device. But since the endpoint is currently in the HAL service itself this implementation is not secure. * This implementation provides most of the functionality, but not the secure UI infrastructure required to run Android Protected Confirmation. Bug: 146078942 Test: VtsHalConfirmationUIV1_0TargetTest Change-Id: I14717b5fa4ef15db960cdd506b8c6fe5369aec8d --- trusty/confirmationui/.clang-format | 10 + trusty/confirmationui/Android.bp | 95 ++++ trusty/confirmationui/NotSoSecureInput.cpp | 207 +++++++ trusty/confirmationui/README | 20 + trusty/confirmationui/TrustyApp.cpp | 156 ++++++ trusty/confirmationui/TrustyApp.h | 155 ++++++ .../confirmationui/TrustyConfirmationUI.cpp | 513 ++++++++++++++++++ trusty/confirmationui/TrustyConfirmationUI.h | 104 ++++ ...dware.confirmationui@1.0-service.trusty.rc | 4 + ...ware.confirmationui@1.0-service.trusty.xml | 11 + .../include/TrustyConfirmationuiHal.h | 33 ++ trusty/confirmationui/service.cpp | 35 ++ 12 files changed, 1343 insertions(+) create mode 100644 trusty/confirmationui/.clang-format create mode 100644 trusty/confirmationui/Android.bp create mode 100644 trusty/confirmationui/NotSoSecureInput.cpp create mode 100644 trusty/confirmationui/README create mode 100644 trusty/confirmationui/TrustyApp.cpp create mode 100644 trusty/confirmationui/TrustyApp.h create mode 100644 trusty/confirmationui/TrustyConfirmationUI.cpp create mode 100644 trusty/confirmationui/TrustyConfirmationUI.h create mode 100644 trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc create mode 100644 trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.xml create mode 100644 trusty/confirmationui/include/TrustyConfirmationuiHal.h create mode 100644 trusty/confirmationui/service.cpp diff --git a/trusty/confirmationui/.clang-format b/trusty/confirmationui/.clang-format new file mode 100644 index 000000000..b0dc94c1d --- /dev/null +++ b/trusty/confirmationui/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +IndentCaseLabels: false +ColumnLimit: 100 +PointerBindsToType: true +SpacesBeforeTrailingComments: 2 diff --git a/trusty/confirmationui/Android.bp b/trusty/confirmationui/Android.bp new file mode 100644 index 000000000..60e0e71b2 --- /dev/null +++ b/trusty/confirmationui/Android.bp @@ -0,0 +1,95 @@ +// 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. +// + +// WARNING: Everything listed here will be built on ALL platforms, +// including x86, the emulator, and the SDK. Modules must be uniquely +// named (liblights.panda), and must build everywhere, or limit themselves +// to only building on ARM if they include assembly. Individual makefiles +// are responsible for having their own logic, for fine-grained control. + +cc_binary { + name: "android.hardware.confirmationui@1.0-service.trusty", + relative_install_path: "hw", + vendor: true, + shared_libs: [ + "android.hardware.confirmationui@1.0", + "android.hardware.confirmationui.not-so-secure-input", + "android.hardware.confirmationui@1.0-lib.trusty", + "libbase", + "libhidlbase", + "libutils", + ], + + init_rc: ["android.hardware.confirmationui@1.0-service.trusty.rc"], + + vintf_fragments: ["android.hardware.confirmationui@1.0-service.trusty.xml"], + + srcs: [ + "service.cpp", + ], + + cflags: [ + "-Wall", + "-Werror", + "-DTEEUI_USE_STD_VECTOR", + ], +} + +cc_library { + name: "android.hardware.confirmationui@1.0-lib.trusty", + vendor: true, + shared_libs: [ + "android.hardware.confirmationui@1.0", + "android.hardware.keymaster@4.0", + "libbase", + "libhidlbase", + "libteeui_hal_support", + "libtrusty", + "libutils", + ], + + export_include_dirs: ["include"], + + srcs: [ + "TrustyApp.cpp", + "TrustyConfirmationUI.cpp", + ], + + cflags: [ + "-Wall", + "-Werror", + "-DTEEUI_USE_STD_VECTOR", + ], +} + +cc_library { + name: "android.hardware.confirmationui.not-so-secure-input", + vendor: true, + shared_libs: [ + "libbase", + "libcrypto", + "libteeui_hal_support", + ], + + srcs: [ + "NotSoSecureInput.cpp", + ], + + cflags: [ + "-Wall", + "-Werror", + "-DTEEUI_USE_STD_VECTOR", + ], +} \ No newline at end of file diff --git a/trusty/confirmationui/NotSoSecureInput.cpp b/trusty/confirmationui/NotSoSecureInput.cpp new file mode 100644 index 000000000..3d9a2d6e5 --- /dev/null +++ b/trusty/confirmationui/NotSoSecureInput.cpp @@ -0,0 +1,207 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace secure_input; + +using teeui::AuthTokenKey; +using teeui::ByteBufferProxy; +using teeui::Hmac; +using teeui::optional; +using teeui::ResponseCode; +using teeui::TestKeyBits; + +constexpr const auto kTestKey = AuthTokenKey::fill(static_cast(TestKeyBits::BYTE)); + +class SecureInputHMacer { + public: + static optional hmac256(const AuthTokenKey& 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 result; + if (!HMAC_Final(&hmacCtx, result.data(), nullptr)) { + return {}; + } + return result; + } +}; + +using HMac = teeui::HMac; + +Nonce generateNonce() { + /* + * Completely random nonce. + * Running the secure input protocol from the HAL service is not secure + * because we don't trust the non-secure world (i.e., HLOS/Android/Linux). So + * using a constant "nonce" here does not weaken security. If this code runs + * on a truly trustworthy source of input events this function needs to return + * hight entropy nonces. + * As of this writing the call to RAND_bytes is commented, because the + * emulator this HAL service runs on does not have a good source of entropy. + * It would block the call to RAND_bytes indefinitely. + */ + Nonce result{0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + // RAND_bytes(result.data(), result.size()); + return result; +} + +/** + * This is an implementation of the SecureInput protocol in unserspace. This is + * just an example and should not be used as is. The protocol implemented her + * should be used by a trusted input device that can assert user events with + * high assurance even if the HLOS kernel is compromised. A confirmationui HAL + * that links directly against this implementation is not secure and shal not be + * used on a production device. + */ +class NotSoSecureInput : public SecureInput { + public: + NotSoSecureInput(HsBeginCb hsBeginCb, HsFinalizeCb hsFinalizeCb, DeliverEventCb deliverEventCb, + InputResultCb inputResultCb) + : hsBeginCb_{hsBeginCb}, hsFinalizeCb_{hsFinalizeCb}, deliverEventCb_{deliverEventCb}, + inputResultCb_{inputResultCb}, discardEvents_{true} {} + + operator bool() const override { return true; } + + void handleEvent(const EventDev& evdev) override { + bool gotEvent; + input_event evt; + std::tie(gotEvent, evt) = evdev.readEvent(); + while (gotEvent) { + if (!(discardEvents_) && evt.type == EV_KEY && + (evt.code == KEY_POWER || evt.code == KEY_VOLUMEDOWN || evt.code == KEY_VOLUMEUP) && + evt.value == 1) { + DTupKeyEvent event = DTupKeyEvent::RESERVED; + + // Translate the event code into DTupKeyEvent which the TA understands. + switch (evt.code) { + case KEY_POWER: + event = DTupKeyEvent::PWR; + break; + case KEY_VOLUMEDOWN: + event = DTupKeyEvent::VOL_DOWN; + break; + case KEY_VOLUMEUP: + event = DTupKeyEvent::VOL_UP; + break; + } + + // The event goes into the HMAC in network byte order. + uint32_t keyEventBE = htobe32(static_cast(event)); + auto signature = HMac::hmac256(kTestKey, kConfirmationUIEventLabel, + teeui::bytesCast(keyEventBE), nCi_); + + teeui::ResponseCode rc; + InputResponse ir; + auto response = std::tie(rc, ir); + if (event != DTupKeyEvent::RESERVED) { + response = deliverEventCb_(event, *signature); + if (rc != ResponseCode::OK) { + LOG(ERROR) << "DeliverInputEvent returned with " << uint32_t(rc); + inputResultCb_(rc); + } else { + switch (ir) { + case InputResponse::OK: + inputResultCb_(rc); + break; + case InputResponse::PENDING_MORE: + rc = performDTUPHandshake(); + if (rc != ResponseCode::OK) { + inputResultCb_(rc); + } + break; + case InputResponse::TIMED_OUT: + inputResultCb_(rc); + break; + } + } + } + } + std::tie(gotEvent, evt) = evdev.readEvent(); + } + } + + void start() override { + auto rc = performDTUPHandshake(); + if (rc != ResponseCode::OK) { + inputResultCb_(rc); + } + discardEvents_ = false; + }; + + private: + teeui::ResponseCode performDTUPHandshake() { + ResponseCode rc; + LOG(INFO) << "Start handshake"; + Nonce nCo; + std::tie(rc, nCo) = hsBeginCb_(); + if (rc != ResponseCode::OK) { + LOG(ERROR) << "Failed to begin secure input handshake (" << uint32_t(rc) << ")"; + return rc; + } + + nCi_ = generateNonce(); + rc = + hsFinalizeCb_(*HMac::hmac256(kTestKey, kConfirmationUIHandshakeLabel, nCo, nCi_), nCi_); + + if (rc != ResponseCode::OK) { + LOG(ERROR) << "Failed to finalize secure input handshake (" << uint32_t(rc) << ")"; + return rc; + } + return ResponseCode::OK; + } + + HsBeginCb hsBeginCb_; + HsFinalizeCb hsFinalizeCb_; + DeliverEventCb deliverEventCb_; + InputResultCb inputResultCb_; + + std::atomic_bool discardEvents_; + Nonce nCi_; +}; + +namespace secure_input { + +std::shared_ptr createSecureInput(SecureInput::HsBeginCb hsBeginCb, + SecureInput::HsFinalizeCb hsFinalizeCb, + SecureInput::DeliverEventCb deliverEventCb, + SecureInput::InputResultCb inputResultCb) { + return std::make_shared(hsBeginCb, hsFinalizeCb, deliverEventCb, + inputResultCb); +} + +} // namespace secure_input diff --git a/trusty/confirmationui/README b/trusty/confirmationui/README new file mode 100644 index 000000000..45d4e7650 --- /dev/null +++ b/trusty/confirmationui/README @@ -0,0 +1,20 @@ +## Secure UI Architecture + +To implement confirmationui a secure UI architecture is required. This entails a way +to display the confirmation dialog driven by a reduced trusted computing base, typically +a trusted execution environment (TEE), without having to rely on Linux and the Android +system for integrity and authenticity of input events. This implementation provides +neither. But it provides most of the functionlity required to run a full Android Protected +Confirmation feature when integrated into a secure UI architecture. + +## Secure input (NotSoSecureInput) + +This implementation does not provide any security guaranties. +The input method (NotSoSecureInput) runs a cryptographic protocols that is +sufficiently secure IFF the end point is implemented on a trustworthy +secure input device. But since the endpoint is currently in the HAL +service itself this implementation is not secure. + +NOTE that a secure input device end point needs a good source of entropy +for generating nonces. The current implementation (NotSoSecureInput.cpp#generateNonce) +uses a constant nonce. \ No newline at end of file diff --git a/trusty/confirmationui/TrustyApp.cpp b/trusty/confirmationui/TrustyApp.cpp new file mode 100644 index 000000000..e4c68f955 --- /dev/null +++ b/trusty/confirmationui/TrustyApp.cpp @@ -0,0 +1,156 @@ +/* + * 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. + */ + +#include "TrustyApp.h" + +#include +#include +#include + +namespace android { +namespace trusty { + +// 0x1000 is the message buffer size but we need to leave some space for a protocol header. +// This assures that packets can always be read/written in one read/write operation. +static constexpr const uint32_t kPacketSize = 0x1000 - 32; + +enum class PacketType : uint32_t { + SND, + RCV, + ACK, +}; + +struct PacketHeader { + PacketType type; + uint32_t remaining; +}; + +const char* toString(PacketType t) { + switch (t) { + case PacketType::SND: + return "SND"; + case PacketType::RCV: + return "RCV"; + case PacketType::ACK: + return "ACK"; + default: + return "UNKNOWN"; + } +} + +static constexpr const uint32_t kHeaderSize = sizeof(PacketHeader); +static constexpr const uint32_t kPayloadSize = kPacketSize - kHeaderSize; + +ssize_t TrustyRpc(int handle, const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin, + uint8_t* iend) { + while (obegin != oend) { + PacketHeader header = { + .type = PacketType::SND, + .remaining = uint32_t(oend - obegin), + }; + uint32_t body_size = std::min(kPayloadSize, header.remaining); + iovec iov[] = { + { + .iov_base = &header, + .iov_len = kHeaderSize, + }, + { + .iov_base = const_cast(obegin), + .iov_len = body_size, + }, + }; + int rc = writev(handle, iov, 2); + if (!rc) { + PLOG(ERROR) << "Error sending SND message. " << rc; + return rc; + } + + obegin += body_size; + + rc = read(handle, &header, kHeaderSize); + if (!rc) { + PLOG(ERROR) << "Error reading ACK. " << rc; + return rc; + } + + if (header.type != PacketType::ACK || header.remaining != oend - obegin) { + LOG(ERROR) << "malformed ACK"; + return -1; + } + } + + ssize_t remaining = 0; + auto begin = ibegin; + do { + PacketHeader header = { + .type = PacketType::RCV, + .remaining = 0, + }; + + iovec iov[] = { + { + .iov_base = &header, + .iov_len = kHeaderSize, + }, + { + .iov_base = begin, + .iov_len = uint32_t(iend - begin), + }, + }; + + ssize_t rc = writev(handle, iov, 1); + if (!rc) { + PLOG(ERROR) << "Error sending RCV message. " << rc; + return rc; + } + + rc = readv(handle, iov, 2); + if (rc < 0) { + PLOG(ERROR) << "Error reading response. " << rc; + return rc; + } + + uint32_t body_size = std::min(kPayloadSize, header.remaining); + if (body_size != rc - kHeaderSize) { + LOG(ERROR) << "Unexpected amount of data: " << rc; + return -1; + } + + remaining = header.remaining - body_size; + begin += body_size; + } while (remaining); + + return begin - ibegin; +} + +TrustyApp::TrustyApp(const std::string& path, const std::string& appname) + : handle_(kInvalidHandle) { + handle_ = tipc_connect(path.c_str(), appname.c_str()); + if (handle_ == kInvalidHandle) { + LOG(ERROR) << AT << "failed to connect to Trusty TA \"" << appname << "\" using dev:" + << "\"" << path << "\""; + } + LOG(INFO) << AT << "succeeded to connect to Trusty TA \"" << appname << "\""; +} +TrustyApp::~TrustyApp() { + if (handle_ != kInvalidHandle) { + tipc_close(handle_); + } + LOG(INFO) << "Done shutting down TrustyApp"; +} + +} // namespace trusty +} // namespace android diff --git a/trusty/confirmationui/TrustyApp.h b/trusty/confirmationui/TrustyApp.h new file mode 100644 index 000000000..05a25f61d --- /dev/null +++ b/trusty/confirmationui/TrustyApp.h @@ -0,0 +1,155 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define AT __FILE__ ":" << __LINE__ << ": " + +namespace android { +namespace trusty { + +using ::teeui::Message; +using ::teeui::msg2tuple_t; +using ::teeui::ReadStream; +using ::teeui::WriteStream; + +#ifndef TEEUI_USE_STD_VECTOR +/* + * TEEUI_USE_STD_VECTOR makes certain wire types like teeui::MsgString and + * teeui::MsgVector be aliases for std::vector. This is required for thread safe + * message serialization. Always compile this with -DTEEUI_USE_STD_VECTOR set in + * CFLAGS of the HAL service. + */ +#error "Must be compiled with -DTEEUI_USE_STD_VECTOR." +#endif + +enum class TrustyAppError : int32_t { + OK, + ERROR = -1, + MSG_TOO_LONG = -2, +}; + +/* + * There is a hard limitation of 0x1800 bytes for the to-be-signed message size. The protocol + * overhead is limited, so that 0x2000 is a buffer size that will be sufficient in any benign + * mode of operation. + */ +static constexpr const size_t kSendBufferSize = 0x2000; + +ssize_t TrustyRpc(int handle, const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin, + uint8_t* iend); + +class TrustyApp { + private: + int handle_; + static constexpr const int kInvalidHandle = -1; + /* + * This mutex serializes communication with the trusted app, not handle_. + * Calling issueCmd during construction or deletion is undefined behavior. + */ + std::mutex mutex_; + + public: + TrustyApp(const std::string& path, const std::string& appname); + ~TrustyApp(); + + template + std::tuple> issueCmd(const T&... args) { + std::lock_guard lock(mutex_); + + if (handle_ == kInvalidHandle) { + LOG(ERROR) << "TrustyApp not connected"; + return {TrustyAppError::ERROR, {}}; + } + + uint8_t buffer[kSendBufferSize]; + WriteStream out(buffer); + + out = write(Request(), out, args...); + if (!out) { + LOG(ERROR) << AT << "send command failed: message formatting"; + return {TrustyAppError::MSG_TOO_LONG, {}}; + } + + auto rc = TrustyRpc(handle_, &buffer[0], const_cast(out.pos()), &buffer[0], + &buffer[kSendBufferSize]); + if (rc < 0) return {TrustyAppError::ERROR, {}}; + + ReadStream in(&buffer[0], rc); + auto result = read(Response(), in); + if (!std::get<0>(result)) { + LOG(ERROR) << "send command failed: message parsing"; + return {TrustyAppError::ERROR, {}}; + } + + return {std::get<0>(result) ? TrustyAppError::OK : TrustyAppError::ERROR, + tuple_tail(std::move(result))}; + } + + template TrustyAppError issueCmd(const T&... args) { + std::lock_guard lock(mutex_); + + if (handle_ == kInvalidHandle) { + LOG(ERROR) << "TrustyApp not connected"; + return TrustyAppError::ERROR; + } + + uint8_t buffer[kSendBufferSize]; + WriteStream out(buffer); + + out = write(Request(), out, args...); + if (!out) { + LOG(ERROR) << AT << "send command failed: message formatting"; + return TrustyAppError::MSG_TOO_LONG; + } + + auto rc = TrustyRpc(handle_, &buffer[0], const_cast(out.pos()), &buffer[0], + &buffer[kSendBufferSize]); + if (rc < 0) { + LOG(ERROR) << "send command failed: " << strerror(errno) << " (" << errno << ")"; + return TrustyAppError::ERROR; + } + + if (rc > 0) { + LOG(ERROR) << "Unexpected non zero length response"; + return TrustyAppError::ERROR; + } + return TrustyAppError::OK; + } + + operator bool() const { return handle_ != kInvalidHandle; } +}; + +} // namespace trusty +} // namespace android diff --git a/trusty/confirmationui/TrustyConfirmationUI.cpp b/trusty/confirmationui/TrustyConfirmationUI.cpp new file mode 100644 index 000000000..6b25893b5 --- /dev/null +++ b/trusty/confirmationui/TrustyConfirmationUI.cpp @@ -0,0 +1,513 @@ +/* + * + * Copyright 2019, 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 "TrustyConfirmationUI.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace confirmationui { +namespace V1_0 { +namespace implementation { + +using namespace secure_input; + +using ::android::trusty::TrustyAppError; + +using ::teeui::AbortMsg; +using ::teeui::DeliverTestCommandMessage; +using ::teeui::DeliverTestCommandResponse; +using ::teeui::FetchConfirmationResult; +using ::teeui::MsgString; +using ::teeui::MsgVector; +using ::teeui::PromptUserConfirmationMsg; +using ::teeui::PromptUserConfirmationResponse; +using ::teeui::ResultMsg; + +using ::secure_input::createSecureInput; + +using ::android::hardware::keymaster::V4_0::HardwareAuthToken; + +using ::std::tie; + +using TeeuiRc = ::teeui::ResponseCode; + +constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0"; +constexpr const char kConfirmationuiAppName[] = "com.android.trusty.confirmationui"; + +namespace { + +class Finalize { + private: + std::function f_; + + public: + Finalize(std::function f) : f_(f) {} + ~Finalize() { + if (f_) f_(); + } + void release() { f_ = {}; } +}; + +ResponseCode convertRc(TeeuiRc trc) { + static_assert( + uint32_t(TeeuiRc::OK) == uint32_t(ResponseCode::OK) && + uint32_t(TeeuiRc::Canceled) == uint32_t(ResponseCode::Canceled) && + uint32_t(TeeuiRc::Aborted) == uint32_t(ResponseCode::Aborted) && + uint32_t(TeeuiRc::OperationPending) == uint32_t(ResponseCode::OperationPending) && + uint32_t(TeeuiRc::Ignored) == uint32_t(ResponseCode::Ignored) && + uint32_t(TeeuiRc::SystemError) == uint32_t(ResponseCode::SystemError) && + uint32_t(TeeuiRc::Unimplemented) == uint32_t(ResponseCode::Unimplemented) && + uint32_t(TeeuiRc::Unexpected) == uint32_t(ResponseCode::Unexpected) && + uint32_t(TeeuiRc::UIError) == uint32_t(ResponseCode::UIError) && + uint32_t(TeeuiRc::UIErrorMissingGlyph) == uint32_t(ResponseCode::UIErrorMissingGlyph) && + uint32_t(TeeuiRc::UIErrorMessageTooLong) == + uint32_t(ResponseCode::UIErrorMessageTooLong) && + uint32_t(TeeuiRc::UIErrorMalformedUTF8Encoding) == + uint32_t(ResponseCode::UIErrorMalformedUTF8Encoding), + "teeui::ResponseCode and " + "::android::hardware::confirmationui::V1_0::Responsecude are out of " + "sync"); + return ResponseCode(trc); +} + +teeui::UIOption convertUIOption(UIOption uio) { + static_assert(uint32_t(UIOption::AccessibilityInverted) == + uint32_t(teeui::UIOption::AccessibilityInverted) && + uint32_t(UIOption::AccessibilityMagnified) == + uint32_t(teeui::UIOption::AccessibilityMagnified), + "teeui::UIOPtion and ::android::hardware::confirmationui::V1_0::UIOption " + "anre out of sync"); + return teeui::UIOption(uio); +} + +inline MsgString hidl2MsgString(const hidl_string& s) { + return {s.c_str(), s.c_str() + s.size()}; +} +template inline MsgVector hidl2MsgVector(const hidl_vec& v) { + return {v}; +} + +inline MsgVector hidl2MsgVector(const hidl_vec& v) { + MsgVector result(v.size()); + for (unsigned int i = 0; i < v.size(); ++i) { + result[i] = convertUIOption(v[i]); + } + return result; +} + +} // namespace + +TrustyConfirmationUI::TrustyConfirmationUI() + : listener_state_(ListenerState::None), prompt_result_(ResponseCode::Ignored) {} + +TrustyConfirmationUI::~TrustyConfirmationUI() { + ListenerState state = listener_state_; + if (state == ListenerState::SetupDone || state == ListenerState::Interactive) { + abort(); + } + if (state != ListenerState::None) { + callback_thread_.join(); + } +} + +std::tuple, MsgVector> +TrustyConfirmationUI::promptUserConfirmation_(const MsgString& promptText, + const MsgVector& extraData, + const MsgString& locale, + const MsgVector& uiOptions) { + std::unique_lock stateLock(listener_state_lock_); + /* + * This is the main listener thread function. The listener thread life cycle + * is equivalent to the life cycle of a single confirmation request. The life + * cycle is devided in four phases. + * * The starting phase: + * * The Trusted App gets loaded and/or the connection to it gets established. + * * A connection to the secure input device is established. + * * The prompt is initiated. This sends all information required by the + * confirmation dialog to the TA. The dialog is not yet displayed. + * * An event loop is created. + * * The event loop listens for user input events, fetches them from the + * secure input device, and delivers them to the TA. + * * All evdev devices are grabbed to give confirmationui exclusive access + * to user input. + * + * Note: During the starting phase the hwbinder service thread is blocked and + * waiting for possible Errors. If the setup phase concludes sucessfully, the + * hwbinder service thread gets unblocked and returns successfully. Errors + * that occur after the first phase are delivered by callback interface. + * + * * The 2nd phase - non interactive phase + * * The event loop thread is started. + * * After a grace period: + * * A handshake between the secure input device SecureInput and the TA + * is performed. + * * The input event handler are armed to process user input events. + * + * * The 3rd phase - interactive phase + * * We wait to any external event + * * Abort + * * Secure user input asserted + * * Secure input delivered (for non interactive VTS testing) + * * The result is fetched from the TA. + * + * * The 4th phase - cleanup + * The cleanup phase is given by the scope of automatic variables created + * in this function. The cleanup commences in reverse order of their creation. + * Here is a list of more complex items in the order in which they go out of + * scope + * * finalizeSecureTouch - signals and joins the secure touch thread. + * * eventloop - signals and joins the event loop thread. The event + * handlers also own all EventDev instances which ungrab the event devices. + * When the eventloop goes out of scope the EventDevs get destroyed + * relinquishing the exclusive hold on the event devices. + * * finalizeConfirmationPrompt - calls abort on the TA, making sure a + * pending operation gets canceled. If the prompt concluded successfully this + * is a spurious call but semantically a no op. + * * secureInput - shuts down the connection to the secure input device + * SecureInput. + * * app - disconnects the TA. Since app is a shared pointer this may not + * unload the app here. It is possible that more instances of the shared + * pointer are held in TrustyConfirmationUI::deliverSecureInputEvent and + * TrustyConfirmationUI::abort. But these instances are extremely short lived + * and it is safe if they are destroyed by either. + * * stateLock - unlocks the listener_state_lock_ if it happens to be held + * at the time of return. + */ + + std::tuple, MsgVector> result; + TeeuiRc& rc = std::get(result); + rc = TeeuiRc::SystemError; + + listener_state_ = ListenerState::Starting; + + auto app = std::make_shared(kTrustyDeviceName, kConfirmationuiAppName); + if (!app) return result; // TeeuiRc::SystemError + + app_ = app; + + auto hsBegin = [&]() -> std::tuple { + auto [error, result] = + app->issueCmd(); + auto& [rc, nCo] = result; + + if (error != TrustyAppError::OK || rc != TeeuiRc::OK) { + LOG(ERROR) << "Failed to begin secure input handshake (" << int32_t(error) << "/" + << uint32_t(rc) << ")"; + rc = error != TrustyAppError::OK ? TeeuiRc::SystemError : rc; + } + return result; + }; + + auto hsFinalize = [&](const Signature& sig, const Nonce& nCi) -> TeeuiRc { + auto [error, finalizeResponse] = + app->issueCmd( + nCi, sig); + auto& [rc] = finalizeResponse; + if (error != TrustyAppError::OK || rc != TeeuiRc::OK) { + LOG(ERROR) << "Failed to finalize secure input handshake (" << int32_t(error) << "/" + << uint32_t(rc) << ")"; + rc = error != TrustyAppError::OK ? TeeuiRc::SystemError : rc; + } + return rc; + }; + + auto deliverInput = [&](DTupKeyEvent event, + const Signature& sig) -> std::tuple { + auto [error, result] = + app->issueCmd(event, sig); + auto& [rc, ir] = result; + if (error != TrustyAppError::OK) { + LOG(ERROR) << "Failed to deliver input command"; + rc = TeeuiRc::SystemError; + } + return result; + }; + + std::atomic eventRC = TeeuiRc::OperationPending; + auto inputResult = [&](TeeuiRc rc) { + TeeuiRc expected = TeeuiRc::OperationPending; + if (eventRC.compare_exchange_strong(expected, rc)) { + listener_state_condv_.notify_all(); + } + }; + + // create Secure Input device. + auto secureInput = createSecureInput(hsBegin, hsFinalize, deliverInput, inputResult); + if (!secureInput || !(*secureInput)) { + LOG(ERROR) << "Failed to open secure input device"; + return result; // TeeuiRc::SystemError; + } + + Finalize finalizeConfirmationPrompt([app] { + LOG(INFO) << "Calling abort for cleanup"; + app->issueCmd(); + }); + + // initiate prompt + LOG(INFO) << "Initiating prompt"; + TrustyAppError error; + auto initResponse = std::tie(rc); + std::tie(error, initResponse) = + app->issueCmd( + promptText, extraData, locale, uiOptions); + if (error == TrustyAppError::MSG_TOO_LONG) { + LOG(ERROR) << "PromptUserConfirmationMsg failed: message too long"; + rc = TeeuiRc::UIErrorMessageTooLong; + return result; + } else if (error != TrustyAppError::OK) { + LOG(ERROR) << "PromptUserConfirmationMsg failed: " << int32_t(error); + return result; // TeeuiRc::SystemError; + } + if (rc != TeeuiRc::OK) { + LOG(ERROR) << "PromptUserConfirmationMsg failed: " << uint32_t(rc); + return result; + } + + LOG(INFO) << "Grabbing event devices"; + EventLoop eventloop; + bool grabbed = + grabAllEvDevsAndRegisterCallbacks(&eventloop, [&](short flags, const EventDev& evDev) { + if (!(flags & POLLIN)) return; + secureInput->handleEvent(evDev); + }); + + if (!grabbed) { + rc = TeeuiRc::SystemError; + return result; + } + + abort_called_ = false; + secureInputDelivered_ = false; + + // ############################## Start 2nd Phase ############################################# + listener_state_ = ListenerState::SetupDone; + stateLock.unlock(); + listener_state_condv_.notify_all(); + + if (!eventloop.start()) { + rc = TeeuiRc::SystemError; + return result; + } + + stateLock.lock(); + + LOG(INFO) << "going to sleep for the grace period"; + auto then = std::chrono::system_clock::now() + + std::chrono::milliseconds(kUserPreInputGracePeriodMillis) + + std::chrono::microseconds(50); + listener_state_condv_.wait_until(stateLock, then, [&]() { return abort_called_; }); + LOG(INFO) << "waking up"; + + if (abort_called_) { + LOG(ERROR) << "Abort called"; + result = {TeeuiRc::Aborted, {}, {}}; + return result; + } + + LOG(INFO) << "Arming event poller"; + // tell the event poller to act on received input events from now on. + secureInput->start(); + + // ############################## Start 3rd Phase - interactive phase ######################### + LOG(INFO) << "Transition to Interactive"; + listener_state_ = ListenerState::Interactive; + stateLock.unlock(); + listener_state_condv_.notify_all(); + + stateLock.lock(); + listener_state_condv_.wait(stateLock, [&]() { + return eventRC != TeeuiRc::OperationPending || abort_called_ || secureInputDelivered_; + }); + LOG(INFO) << "Listener waking up"; + if (abort_called_) { + LOG(ERROR) << "Abort called"; + result = {TeeuiRc::Aborted, {}, {}}; + return result; + } + + if (!secureInputDelivered_) { + if (eventRC != TeeuiRc::OK) { + LOG(ERROR) << "Bad input response"; + result = {eventRC, {}, {}}; + return result; + } + } + + stateLock.unlock(); + + LOG(INFO) << "Fetching Result"; + std::tie(error, result) = app->issueCmd(); + LOG(INFO) << "Result yields " << int32_t(error) << "/" << uint32_t(rc); + if (error != TrustyAppError::OK) { + result = {TeeuiRc::SystemError, {}, {}}; + } + return result; + + // ############################## Start 4th Phase - cleanup ################################## +} + +// Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI +// follow. +Return TrustyConfirmationUI::promptUserConfirmation( + const sp& resultCB, const hidl_string& promptText, + const hidl_vec& extraData, const hidl_string& locale, + const hidl_vec& uiOptions) { + std::unique_lock stateLock(listener_state_lock_, std::defer_lock); + if (!stateLock.try_lock()) { + return ResponseCode::OperationPending; + } + switch (listener_state_) { + case ListenerState::None: + break; + case ListenerState::Starting: + case ListenerState::SetupDone: + case ListenerState::Interactive: + return ResponseCode::OperationPending; + case ListenerState::Terminating: + callback_thread_.join(); + listener_state_ = ListenerState::None; + break; + default: + return ResponseCode::Unexpected; + } + + assert(listener_state_ == ListenerState::None); + + callback_thread_ = std::thread( + [this](sp resultCB, hidl_string promptText, + hidl_vec extraData, hidl_string locale, hidl_vec uiOptions) { + auto [trc, msg, token] = + promptUserConfirmation_(hidl2MsgString(promptText), hidl2MsgVector(extraData), + hidl2MsgString(locale), hidl2MsgVector(uiOptions)); + bool do_callback = (listener_state_ == ListenerState::Interactive || + listener_state_ == ListenerState::SetupDone) && + resultCB; + prompt_result_ = convertRc(trc); + listener_state_ = ListenerState::Terminating; + if (do_callback) { + auto error = resultCB->result(prompt_result_, msg, token); + if (!error.isOk()) { + LOG(ERROR) << "Result callback failed " << error.description(); + } + } else { + listener_state_condv_.notify_all(); + } + }, + resultCB, promptText, extraData, locale, uiOptions); + + listener_state_condv_.wait(stateLock, [this] { + return listener_state_ == ListenerState::SetupDone || + listener_state_ == ListenerState::Interactive || + listener_state_ == ListenerState::Terminating; + }); + if (listener_state_ == ListenerState::Terminating) { + callback_thread_.join(); + listener_state_ = ListenerState::None; + return prompt_result_; + } + return ResponseCode::OK; +} + +Return +TrustyConfirmationUI::deliverSecureInputEvent(const HardwareAuthToken& secureInputToken) { + ResponseCode rc = ResponseCode::Ignored; + { + /* + * deliverSecureInputEvent is only used by the VTS test to mock human input. A correct + * implementation responds with a mock confirmation token signed with a test key. The + * problem is that the non interactive grace period was not formalized in the HAL spec, + * so that the VTS test does not account for the grace period. (It probably should.) + * This means we can only pass the VTS test if we block until the grace period is over + * (SetupDone -> Interactive) before we deliver the input event. + * + * The true secure input is delivered by a different mechanism and gets ignored - + * not queued - until the grace period is over. + * + */ + std::unique_lock stateLock(listener_state_lock_); + listener_state_condv_.wait(stateLock, + [this] { return listener_state_ != ListenerState::SetupDone; }); + + if (listener_state_ != ListenerState::Interactive) return ResponseCode::Ignored; + auto sapp = app_.lock(); + if (!sapp) return ResponseCode::Ignored; + auto [error, response] = + sapp->issueCmd( + static_cast(secureInputToken.challenge)); + if (error != TrustyAppError::OK) return ResponseCode::SystemError; + auto& [trc] = response; + if (trc != TeeuiRc::Ignored) secureInputDelivered_ = true; + rc = convertRc(trc); + } + if (secureInputDelivered_) listener_state_condv_.notify_all(); + // VTS test expect an OK response if the event was successfully delivered. + // But since the TA returns the callback response now, we have to translate + // Canceled into OK. Canceled is only returned if the delivered event canceled + // the operation, which means that the event was successfully delivered. Thus + // we return OK. + if (rc == ResponseCode::Canceled) return ResponseCode::OK; + return rc; +} + +Return TrustyConfirmationUI::abort() { + { + std::unique_lock stateLock(listener_state_lock_); + if (listener_state_ == ListenerState::SetupDone || + listener_state_ == ListenerState::Interactive) { + auto sapp = app_.lock(); + if (sapp) sapp->issueCmd(); + abort_called_ = true; + } + } + listener_state_condv_.notify_all(); + return Void(); +} + +android::sp createTrustyConfirmationUI() { + return new TrustyConfirmationUI(); +} + +} // namespace implementation +} // namespace V1_0 +} // namespace confirmationui +} // namespace hardware +} // namespace android diff --git a/trusty/confirmationui/TrustyConfirmationUI.h b/trusty/confirmationui/TrustyConfirmationUI.h new file mode 100644 index 000000000..3a7c7ef4a --- /dev/null +++ b/trusty/confirmationui/TrustyConfirmationUI.h @@ -0,0 +1,104 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H +#define ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "TrustyApp.h" + +namespace android { +namespace hardware { +namespace confirmationui { +namespace V1_0 { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; + +using ::android::trusty::TrustyApp; + +class TrustyConfirmationUI : public IConfirmationUI { + public: + TrustyConfirmationUI(); + virtual ~TrustyConfirmationUI(); + // Methods from ::android::hardware::confirmationui::V1_0::IConfirmationUI + // follow. + Return promptUserConfirmation(const sp& resultCB, + const hidl_string& promptText, + const hidl_vec& extraData, + const hidl_string& locale, + const hidl_vec& uiOptions) override; + Return deliverSecureInputEvent( + const ::android::hardware::keymaster::V4_0::HardwareAuthToken& secureInputToken) override; + Return abort() override; + + private: + std::weak_ptr app_; + std::thread callback_thread_; + + enum class ListenerState : uint32_t { + None, + Starting, + SetupDone, + Interactive, + Terminating, + }; + + /* + * listener_state is protected by listener_state_lock. It makes transitions between phases + * of the confirmation operation atomic. + * (See TrustyConfirmationUI.cpp#promptUserConfirmation_ for details about operation phases) + */ + ListenerState listener_state_; + /* + * abort_called_ is also protected by listener_state_lock_ and indicates that the HAL user + * called abort. + */ + bool abort_called_; + std::mutex listener_state_lock_; + std::condition_variable listener_state_condv_; + ResponseCode prompt_result_; + bool secureInputDelivered_; + + std::tuple, teeui::MsgVector> + promptUserConfirmation_(const teeui::MsgString& promptText, + const teeui::MsgVector& extraData, + const teeui::MsgString& locale, + const teeui::MsgVector& uiOptions); +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace confirmationui +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H diff --git a/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc b/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc new file mode 100644 index 000000000..dc7a03b6e --- /dev/null +++ b/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc @@ -0,0 +1,4 @@ +service confirmationui-1-0 /vendor/bin/hw/android.hardware.confirmationui@1.0-service.trusty + class hal + user nobody + group drmrpc input diff --git a/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.xml b/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.xml new file mode 100644 index 000000000..9008b872e --- /dev/null +++ b/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.xml @@ -0,0 +1,11 @@ + + + android.hardware.confirmationui + hwbinder + 1.0 + + IConfirmationUI + default + + + diff --git a/trusty/confirmationui/include/TrustyConfirmationuiHal.h b/trusty/confirmationui/include/TrustyConfirmationuiHal.h new file mode 100644 index 000000000..2ab9389b1 --- /dev/null +++ b/trusty/confirmationui/include/TrustyConfirmationuiHal.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace android { +namespace hardware { +namespace confirmationui { +namespace V1_0 { +namespace implementation { + +android::sp createTrustyConfirmationUI(); + +} // namespace implementation +} // namespace V1_0 +} // namespace confirmationui +} // namespace hardware +} // namespace android diff --git a/trusty/confirmationui/service.cpp b/trusty/confirmationui/service.cpp new file mode 100644 index 000000000..dd7e84b44 --- /dev/null +++ b/trusty/confirmationui/service.cpp @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#include +#include + +#include + +using android::sp; +using android::hardware::confirmationui::V1_0::implementation::createTrustyConfirmationUI; + +int main() { + ::android::hardware::configureRpcThreadpool(1, true /*willJoinThreadpool*/); + auto service = createTrustyConfirmationUI(); + auto status = service->registerAsService(); + if (status != android::OK) { + LOG(FATAL) << "Could not register service for ConfirmationUI 1.0 (" << status << ")"; + return -1; + } + ::android::hardware::joinRpcThreadpool(); + return -1; +}