| /* |
| * |
| * 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 <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 aidl::android::hardware::confirmationui { |
| using namespace secure_input; |
| |
| using ::android::trusty::confirmationui::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 ::std::tie; |
| |
| using TeeuiRc = ::teeui::ResponseCode; |
| |
| constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0"; |
| constexpr const char kConfirmationuiAppName[] = CONFIRMATIONUI_PORT; |
| |
| namespace { |
| |
| class Finalize { |
| private: |
| std::function<void()> f_; |
| |
| public: |
| Finalize(std::function<void()> f) : f_(f) {} |
| ~Finalize() { |
| if (f_) f_(); |
| } |
| void release() { f_ = {}; } |
| }; |
| |
| int convertRc(TeeuiRc trc) { |
| static_assert( |
| uint32_t(TeeuiRc::OK) == uint32_t(IConfirmationUI::OK) && |
| uint32_t(TeeuiRc::Canceled) == uint32_t(IConfirmationUI::CANCELED) && |
| uint32_t(TeeuiRc::Aborted) == uint32_t(IConfirmationUI::ABORTED) && |
| uint32_t(TeeuiRc::OperationPending) == uint32_t(IConfirmationUI::OPERATION_PENDING) && |
| uint32_t(TeeuiRc::Ignored) == uint32_t(IConfirmationUI::IGNORED) && |
| uint32_t(TeeuiRc::SystemError) == uint32_t(IConfirmationUI::SYSTEM_ERROR) && |
| uint32_t(TeeuiRc::Unimplemented) == uint32_t(IConfirmationUI::UNIMPLEMENTED) && |
| uint32_t(TeeuiRc::Unexpected) == uint32_t(IConfirmationUI::UNEXPECTED) && |
| uint32_t(TeeuiRc::UIError) == uint32_t(IConfirmationUI::UI_ERROR) && |
| uint32_t(TeeuiRc::UIErrorMissingGlyph) == |
| uint32_t(IConfirmationUI::UI_ERROR_MISSING_GLYPH) && |
| uint32_t(TeeuiRc::UIErrorMessageTooLong) == |
| uint32_t(IConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG) && |
| uint32_t(TeeuiRc::UIErrorMalformedUTF8Encoding) == |
| uint32_t(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING), |
| "teeui::ResponseCode and " |
| "::android::hardware::confirmationui::V1_0::Responsecude are out of " |
| "sync"); |
| return static_cast<int>(trc); |
| } |
| |
| teeui::UIOption convertUIOption(UIOption uio) { |
| static_assert(uint32_t(UIOption::ACCESSIBILITY_INVERTED) == |
| uint32_t(teeui::UIOption::AccessibilityInverted) && |
| uint32_t(UIOption::ACCESSIBILITY_MAGNIFIED) == |
| uint32_t(teeui::UIOption::AccessibilityMagnified), |
| "teeui::UIOPtion and ::android::hardware::confirmationui::V1_0::UIOption " |
| "are out of sync"); |
| return teeui::UIOption(uio); |
| } |
| |
| inline MsgString stdString2MsgString(const string& s) { |
| return {s.c_str(), s.c_str() + s.size()}; |
| } |
| template <typename T> inline MsgVector<T> stdVector2MsgVector(const vector<T>& v) { |
| return {v}; |
| } |
| |
| inline MsgVector<teeui::UIOption> stdVector2MsgVector(const vector<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_(IConfirmationUI::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 ::aidl::android::hardware::confirmationui::IConfirmationUI |
| // follow. |
| ::ndk::ScopedAStatus TrustyConfirmationUI::promptUserConfirmation( |
| const shared_ptr<IConfirmationResultCallback>& resultCB, const vector<uint8_t>& promptTextBytes, |
| const vector<uint8_t>& extraData, const string& locale, const vector<UIOption>& uiOptions) { |
| std::unique_lock<std::mutex> stateLock(listener_state_lock_, std::defer_lock); |
| string promptText(promptTextBytes.begin(), promptTextBytes.end()); |
| if (!stateLock.try_lock()) { |
| return ndk::ScopedAStatus( |
| AStatus_fromServiceSpecificError(IConfirmationUI::OPERATION_PENDING)); |
| } |
| switch (listener_state_) { |
| case ListenerState::None: |
| break; |
| case ListenerState::Starting: |
| case ListenerState::SetupDone: |
| case ListenerState::Interactive: |
| return ndk::ScopedAStatus( |
| AStatus_fromServiceSpecificError(IConfirmationUI::OPERATION_PENDING)); |
| case ListenerState::Terminating: |
| callback_thread_.join(); |
| listener_state_ = ListenerState::None; |
| break; |
| default: |
| return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::UNEXPECTED)); |
| } |
| |
| assert(listener_state_ == ListenerState::None); |
| |
| callback_thread_ = std::thread( |
| [this](const shared_ptr<IConfirmationResultCallback>& resultCB, const string& promptText, |
| const vector<uint8_t>& extraData, const string& locale, |
| const vector<UIOption>& uiOptions) { |
| auto [trc, msg, token] = promptUserConfirmation_( |
| stdString2MsgString(promptText), stdVector2MsgVector(extraData), |
| stdString2MsgString(locale), stdVector2MsgVector(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.getDescription(); |
| } |
| } 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 ndk::ScopedAStatus(AStatus_fromServiceSpecificError(prompt_result_)); |
| } |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ::ndk::ScopedAStatus |
| TrustyConfirmationUI::deliverSecureInputEvent(const HardwareAuthToken& secureInputToken) { |
| int rc = IConfirmationUI::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 ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::IGNORED)); |
| auto sapp = app_.lock(); |
| if (!sapp) |
| return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::IGNORED)); |
| auto [error, response] = |
| sapp->issueCmd<DeliverTestCommandMessage, DeliverTestCommandResponse>( |
| static_cast<teeui::TestModeCommands>(secureInputToken.challenge)); |
| if (error != TrustyAppError::OK) |
| return ndk::ScopedAStatus( |
| AStatus_fromServiceSpecificError(IConfirmationUI::SYSTEM_ERROR)); |
| 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 == IConfirmationUI::CANCELED) return ndk::ScopedAStatus::ok(); |
| if (rc != IConfirmationUI::OK) { |
| return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(rc)); |
| } |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ::ndk::ScopedAStatus 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 ndk::ScopedAStatus::ok(); |
| } |
| |
| std::shared_ptr<IConfirmationUI> createTrustyConfirmationUI() { |
| return ndk::SharedRefBase::make<TrustyConfirmationUI>(); |
| } |
| |
| } // namespace aidl::android::hardware::confirmationui |