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
This commit is contained in:
Janis Danisevskis 2020-01-13 14:24:32 -08:00
parent d8a8988c6e
commit 8fe0cfb098
12 changed files with 1343 additions and 0 deletions

View file

@ -0,0 +1,10 @@
BasedOnStyle: LLVM
IndentWidth: 4
UseTab: Never
BreakBeforeBraces: Attach
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: true
IndentCaseLabels: false
ColumnLimit: 100
PointerBindsToType: true
SpacesBeforeTrailingComments: 2

View file

@ -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",
],
}

View file

@ -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 <android-base/logging.h>
#include <endian.h>
#include <memory>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <secure_input/evdev.h>
#include <secure_input/secure_input_device.h>
#include <teeui/utils.h>
#include <initializer_list>
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<uint8_t>(TestKeyBits::BYTE));
class SecureInputHMacer {
public:
static optional<Hmac> hmac256(const AuthTokenKey& key,
std::initializer_list<ByteBufferProxy> 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<SecureInputHMacer>;
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<uint32_t>(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<SecureInput> createSecureInput(SecureInput::HsBeginCb hsBeginCb,
SecureInput::HsFinalizeCb hsFinalizeCb,
SecureInput::DeliverEventCb deliverEventCb,
SecureInput::InputResultCb inputResultCb) {
return std::make_shared<NotSoSecureInput>(hsBeginCb, hsFinalizeCb, deliverEventCb,
inputResultCb);
}
} // namespace secure_input

View file

@ -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.

View file

@ -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 <android-base/logging.h>
#include <sys/uio.h>
#include <trusty/tipc.h>
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<uint8_t*>(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

View file

@ -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 <android-base/logging.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <sys/eventfd.h>
#include <sys/stat.h>
#include <teeui/msg_formatting.h>
#include <trusty/tipc.h>
#include <unistd.h>
#include <fstream>
#include <functional>
#include <future>
#include <iostream>
#include <sstream>
#include <thread>
#include <vector>
#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 <typename Request, typename Response, typename... T>
std::tuple<TrustyAppError, msg2tuple_t<Response>> issueCmd(const T&... args) {
std::lock_guard<std::mutex> 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<const uint8_t*>(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 <typename Request, typename... T> TrustyAppError issueCmd(const T&... args) {
std::lock_guard<std::mutex> 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<const uint8_t*>(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

View file

@ -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 <android-base/logging.h>
#include <android/hardware/confirmationui/1.0/types.h>
#include <android/hardware/keymaster/4.0/types.h>
#include <fcntl.h>
#include <linux/input.h>
#include <poll.h>
#include <pthread.h>
#include <secure_input/evdev.h>
#include <secure_input/secure_input_device.h>
#include <secure_input/secure_input_proto.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <teeui/msg_formatting.h>
#include <teeui/utils.h>
#include <time.h>
#include <atomic>
#include <functional>
#include <memory>
#include <thread>
#include <tuple>
#include <vector>
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<void()> f_;
public:
Finalize(std::function<void()> 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 <typename T> inline MsgVector<T> hidl2MsgVector(const hidl_vec<T>& v) {
return {v};
}
inline MsgVector<teeui::UIOption> hidl2MsgVector(const hidl_vec<UIOption>& v) {
MsgVector<teeui::UIOption> 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<TeeuiRc, MsgVector<uint8_t>, MsgVector<uint8_t>>
TrustyConfirmationUI::promptUserConfirmation_(const MsgString& promptText,
const MsgVector<uint8_t>& extraData,
const MsgString& locale,
const MsgVector<teeui::UIOption>& uiOptions) {
std::unique_lock<std::mutex> 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<TeeuiRc, MsgVector<uint8_t>, MsgVector<uint8_t>> result;
TeeuiRc& rc = std::get<TeeuiRc>(result);
rc = TeeuiRc::SystemError;
listener_state_ = ListenerState::Starting;
auto app = std::make_shared<TrustyApp>(kTrustyDeviceName, kConfirmationuiAppName);
if (!app) return result; // TeeuiRc::SystemError
app_ = app;
auto hsBegin = [&]() -> std::tuple<TeeuiRc, Nonce> {
auto [error, result] =
app->issueCmd<secure_input::InputHandshake, secure_input::InputHandshakeResponse>();
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<FinalizeInputSessionHandshake, FinalizeInputSessionHandshakeResponse>(
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<TeeuiRc, InputResponse> {
auto [error, result] =
app->issueCmd<DeliverInputEvent, DeliverInputEventResponse>(event, sig);
auto& [rc, ir] = result;
if (error != TrustyAppError::OK) {
LOG(ERROR) << "Failed to deliver input command";
rc = TeeuiRc::SystemError;
}
return result;
};
std::atomic<TeeuiRc> 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<AbortMsg>();
});
// initiate prompt
LOG(INFO) << "Initiating prompt";
TrustyAppError error;
auto initResponse = std::tie(rc);
std::tie(error, initResponse) =
app->issueCmd<PromptUserConfirmationMsg, PromptUserConfirmationResponse>(
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<FetchConfirmationResult, ResultMsg>();
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<ResponseCode> TrustyConfirmationUI::promptUserConfirmation(
const sp<IConfirmationResultCallback>& resultCB, const hidl_string& promptText,
const hidl_vec<uint8_t>& extraData, const hidl_string& locale,
const hidl_vec<UIOption>& uiOptions) {
std::unique_lock<std::mutex> 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<IConfirmationResultCallback> resultCB, hidl_string promptText,
hidl_vec<uint8_t> extraData, hidl_string locale, hidl_vec<UIOption> 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<ResponseCode>
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<std::mutex> 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<DeliverTestCommandMessage, DeliverTestCommandResponse>(
static_cast<teeui::TestModeCommands>(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<void> TrustyConfirmationUI::abort() {
{
std::unique_lock<std::mutex> stateLock(listener_state_lock_);
if (listener_state_ == ListenerState::SetupDone ||
listener_state_ == ListenerState::Interactive) {
auto sapp = app_.lock();
if (sapp) sapp->issueCmd<AbortMsg>();
abort_called_ = true;
}
}
listener_state_condv_.notify_all();
return Void();
}
android::sp<IConfirmationUI> createTrustyConfirmationUI() {
return new TrustyConfirmationUI();
}
} // namespace implementation
} // namespace V1_0
} // namespace confirmationui
} // namespace hardware
} // namespace android

View file

@ -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 <android/hardware/confirmationui/1.0/IConfirmationUI.h>
#include <android/hardware/keymaster/4.0/types.h>
#include <hidl/Status.h>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <teeui/generic_messages.h>
#include <thread>
#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<ResponseCode> promptUserConfirmation(const sp<IConfirmationResultCallback>& resultCB,
const hidl_string& promptText,
const hidl_vec<uint8_t>& extraData,
const hidl_string& locale,
const hidl_vec<UIOption>& uiOptions) override;
Return<ResponseCode> deliverSecureInputEvent(
const ::android::hardware::keymaster::V4_0::HardwareAuthToken& secureInputToken) override;
Return<void> abort() override;
private:
std::weak_ptr<TrustyApp> 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::ResponseCode, teeui::MsgVector<uint8_t>, teeui::MsgVector<uint8_t>>
promptUserConfirmation_(const teeui::MsgString& promptText,
const teeui::MsgVector<uint8_t>& extraData,
const teeui::MsgString& locale,
const teeui::MsgVector<teeui::UIOption>& uiOptions);
};
} // namespace implementation
} // namespace V1_0
} // namespace confirmationui
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_CONFIRMATIONUI_V1_0_TRUSTY_CONFIRMATIONUI_H

View file

@ -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

View file

@ -0,0 +1,11 @@
<manifest version="1.0" type="device">
<hal format="hidl">
<name>android.hardware.confirmationui</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IConfirmationUI</name>
<instance>default</instance>
</interface>
</hal>
</manifest>

View file

@ -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 <android/hardware/confirmationui/1.0/IConfirmationUI.h>
namespace android {
namespace hardware {
namespace confirmationui {
namespace V1_0 {
namespace implementation {
android::sp<IConfirmationUI> createTrustyConfirmationUI();
} // namespace implementation
} // namespace V1_0
} // namespace confirmationui
} // namespace hardware
} // namespace android

View file

@ -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 <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
#include <TrustyConfirmationuiHal.h>
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;
}