diff options
author | Janis Danisevskis <jdanis@google.com> | 2017-11-03 12:18:34 -0700 |
---|---|---|
committer | Janis Danisevskis <jdanis@google.com> | 2018-01-24 10:10:55 -0800 |
commit | a9f0fb0db1e62168534c5a27e123e1413b818586 (patch) | |
tree | e0abc9e47ba9edaaeaa305b64e52edd56df8b623 /confirmationui | |
parent | b70f2b2521f518fe85ffd03948ac52948187d306 (diff) | |
download | android_hardware_interfaces-a9f0fb0db1e62168534c5a27e123e1413b818586.tar.gz android_hardware_interfaces-a9f0fb0db1e62168534c5a27e123e1413b818586.tar.bz2 android_hardware_interfaces-a9f0fb0db1e62168534c5a27e123e1413b818586.zip |
Add confirmation UI support libaray
Includes:
A light weight CBOR generator
This patch adds a header only CBOR generator and unit tests.
It allows expressing CBOR datastructures in C++ for subsequent
serialization. The implementation performs no memory allocation
and only depends on stdint.h (for (u)intx_t) and stddefs.h (for size_t).
It is tailored for use in constrained environments such as TEEs.
Convenience method for generating a SHA256 HMAC
Bug: 63928580
Test: android.hardware.confirmationui@support-lib-tests
Change-Id: I1d93a85503f861281e71e09b1ede5cbb74219694
Diffstat (limited to 'confirmationui')
11 files changed, 1758 insertions, 0 deletions
diff --git a/confirmationui/support/Android.bp b/confirmationui/support/Android.bp new file mode 100644 index 000000000..62156b341 --- /dev/null +++ b/confirmationui/support/Android.bp @@ -0,0 +1,51 @@ +// +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_library { + name: "android.hardware.confirmationui-support-lib", + vendor_available: true, + host_supported: true, + vndk: { + enabled: true, + }, + srcs: [ + "src/cbor.cpp", + "src/confirmationui_utils.cpp", + ], + export_include_dirs: [ + "include", + ] +} + +cc_test { + name: "android.hardware.confirmationui-support-lib-tests", + srcs: [ + "test/gtest_main.cpp", + "test/android_cbor_test.cpp", + "test/msg_formatting_test.cpp", + ], + static_libs: [ + "libgtest", + "android.hardware.confirmationui-support-lib", + ], + shared_libs: [ + "android.hardware.confirmationui@1.0", + "android.hardware.keymaster@4.0", + "libhidlbase", + ], + clang: true, + cflags: [ "-O0" ], +} diff --git a/confirmationui/support/OWNERS b/confirmationui/support/OWNERS new file mode 100644 index 000000000..335660da3 --- /dev/null +++ b/confirmationui/support/OWNERS @@ -0,0 +1,2 @@ +jdanis@google.com +swillden@google.com diff --git a/confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h b/confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h new file mode 100644 index 000000000..a88cd40e3 --- /dev/null +++ b/confirmationui/support/include/android/hardware/confirmationui/1.0/generic/GenericOperation.h @@ -0,0 +1,188 @@ +/* +** +** Copyright 2017, 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 CONFIRMATIONUI_1_0_DEFAULT_GENERICOPERATION_H_ +#define CONFIRMATIONUI_1_0_DEFAULT_GENERICOPERATION_H_ + +#include <android/hardware/confirmationui/1.0/types.h> +#include <android/hardware/confirmationui/support/cbor.h> +#include <android/hardware/confirmationui/support/confirmationui_utils.h> +#include <android/hardware/keymaster/4.0/types.h> + +namespace android { +namespace hardware { +namespace confirmationui { +namespace V1_0 { +namespace generic { + +namespace { +using namespace ::android::hardware::confirmationui::support; +using ::android::hardware::keymaster::V4_0::HardwareAuthToken; +using ::android::hardware::keymaster::V4_0::HardwareAuthenticatorType; + +inline bool hasOption(UIOption option, const hidl_vec<UIOption>& uiOptions) { + for (auto& o : uiOptions) { + if (o == option) return true; + } + return false; +} + +template <typename Callback, typename TimeStamper, typename HmacImplementation> +class Operation { + using HMacer = support::HMac<HmacImplementation>; + + public: + Operation() : error_(ResponseCode::Ignored), formattedMessageLength_(0) {} + + ResponseCode init(const Callback& resultCB, const hidl_string& promptText, + const hidl_vec<uint8_t>& extraData, const hidl_string& locale, + const hidl_vec<UIOption>& uiOptions) { + (void)locale; + (void)uiOptions; + resultCB_ = resultCB; + if (error_ != ResponseCode::Ignored) return ResponseCode::OperationPending; + // TODO make copy of promptText before using it may reside in shared buffer + auto state = write( + WriteState(formattedMessageBuffer_), + map(pair(text("prompt"), text(promptText)), pair(text("extra"), bytes(extraData)))); + switch (state.error_) { + case Error::OK: + break; + case Error::OUT_OF_DATA: + return ResponseCode::UIErrorMessageTooLong; + case Error::MALFORMED_UTF8: + return ResponseCode::UIErrorMalformedUTF8Encoding; + case Error::MALFORMED: + default: + return ResponseCode::Unexpected; + } + formattedMessageLength_ = state.data_ - formattedMessageBuffer_; + // setup TUI and diagnose more UI errors here. + // on success record the start time + startTime_ = TimeStamper::now(); + if (!startTime_.isOk()) { + return ResponseCode::SystemError; + } + error_ = ResponseCode::OK; + return ResponseCode::OK; + } + + void setHmacKey(const uint8_t (&key)[32]) { hmacKey_ = {key}; } + + void abort() { + // tear down TUI here + if (isPending()) { + resultCB_->result(ResponseCode::Aborted, {}, {}); + error_ = ResponseCode::Ignored; + } + } + + void userCancel() { + // tear down TUI here + if (isPending()) error_ = ResponseCode::Canceled; + } + + void finalize(const uint8_t key[32]) { + if (error_ == ResponseCode::Ignored) return; + resultCB_->result(error_, getMessage(), userConfirm(key)); + error_ = ResponseCode::Ignored; + resultCB_ = {}; + } + + bool isPending() const { return error_ != ResponseCode::Ignored; } + + static Operation& get() { + static Operation operation; + return operation; + } + + ResponseCode deliverSecureInputEvent(const HardwareAuthToken& secureInputToken) { + constexpr uint8_t testKeyByte = static_cast<uint8_t>(TestKeyBits::BYTE); + constexpr uint8_t testKey[32] = {testKeyByte, testKeyByte, testKeyByte, testKeyByte, + testKeyByte, testKeyByte, testKeyByte, testKeyByte, + testKeyByte, testKeyByte, testKeyByte, testKeyByte, + testKeyByte, testKeyByte, testKeyByte, testKeyByte}; + + auto hmac = HMacer::hmac256(testKey, "\0", bytes_cast(secureInputToken.challenge), + bytes_cast(secureInputToken.userId), + bytes_cast(secureInputToken.authenticatorId), + bytes_cast(hton(secureInputToken.authenticatorType)), + bytes_cast(hton(secureInputToken.timestamp))); + if (!hmac.isOk()) return ResponseCode::Unexpected; + if (hmac.value() == secureInputToken.mac) { + // okay so this is a test token + switch (static_cast<TestModeCommands>(secureInputToken.challenge)) { + case TestModeCommands::OK_EVENT: { + if (isPending()) { + finalize(testKey); + return ResponseCode::OK; + } else { + return ResponseCode::Ignored; + } + } + case TestModeCommands::CANCEL_EVENT: { + bool ignored = !isPending(); + userCancel(); + finalize(testKey); + return ignored ? ResponseCode::Ignored : ResponseCode::OK; + } + default: + return ResponseCode::Ignored; + } + } + return ResponseCode::Ignored; + } + + private: + bool acceptAuthToken(const HardwareAuthToken&) { return false; } + hidl_vec<uint8_t> getMessage() { + hidl_vec<uint8_t> result; + if (error_ != ResponseCode::OK) return {}; + result.setToExternal(formattedMessageBuffer_, formattedMessageLength_); + return result; + } + hidl_vec<uint8_t> userConfirm(const uint8_t key[32]) { + // tear down TUI here + if (error_ != ResponseCode::OK) return {}; + confirmationTokenScratchpad_ = HMacer::hmac256(key, "confirmation token", getMessage()); + if (!confirmationTokenScratchpad_.isOk()) { + error_ = ResponseCode::Unexpected; + return {}; + } + hidl_vec<uint8_t> result; + result.setToExternal(confirmationTokenScratchpad_->data(), + confirmationTokenScratchpad_->size()); + return result; + } + + ResponseCode error_; + uint8_t formattedMessageBuffer_[uint32_t(MessageSize::MAX)]; + size_t formattedMessageLength_; + NullOr<array<uint8_t, 32>> confirmationTokenScratchpad_; + Callback resultCB_; + typename TimeStamper::TimeStamp startTime_; + NullOr<array<uint8_t, 32>> hmacKey_; +}; + +} // namespace +} // namespace generic +} // namespace V1_0 +} // namespace confirmationui +} // namespace hardware +} // namespace android + +#endif // CONFIRMATIONUI_1_0_DEFAULT_GENERICOPERATION_H_ diff --git a/confirmationui/support/include/android/hardware/confirmationui/support/cbor.h b/confirmationui/support/include/android/hardware/confirmationui/support/cbor.h new file mode 100644 index 000000000..f5814d4f0 --- /dev/null +++ b/confirmationui/support/include/android/hardware/confirmationui/support/cbor.h @@ -0,0 +1,335 @@ +/* +** +** Copyright 2017, 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 CONFIRMATIONUI_1_0_DEFAULT_CBOR_H_ +#define CONFIRMATIONUI_1_0_DEFAULT_CBOR_H_ + +#include <stddef.h> +#include <stdint.h> +#include <type_traits> + +namespace android { +namespace hardware { +namespace confirmationui { +namespace support { + +template <typename In, typename Out> +Out copy(In begin, In end, Out out) { + while (begin != end) { + *out++ = *begin++; + } + return out; +} + +enum class Type : uint8_t { + NUMBER = 0, + NEGATIVE = 1, + BYTE_STRING = 2, + TEXT_STRING = 3, + ARRAY = 4, + MAP = 5, + TAG = 6, + FLOAT = 7, +}; + +enum class Error : uint32_t { + OK = 0, + OUT_OF_DATA = 1, + MALFORMED = 2, + MALFORMED_UTF8 = 3, +}; + +template <typename Key, typename Value> +struct MapElement { + const Key& key_; + const Value& value_; + MapElement(const Key& key, const Value& value) : key_(key), value_(value) {} +}; + +template <typename... Elems> +struct Array; + +template <typename Head, typename... Tail> +struct Array<Head, Tail...> { + const Head& head_; + Array<Tail...> tail_; + Array(const Head& head, const Tail&... tail) : head_(head), tail_(tail...) {} + constexpr size_t size() const { return sizeof...(Tail) + 1; }; +}; + +template <> +struct Array<> {}; + +struct TextStr {}; +struct ByteStr {}; + +template <typename T, typename Variant> +struct StringBuffer { + const T* data_; + size_t size_; + StringBuffer(const T* data, size_t size) : data_(data), size_(size) { + static_assert(sizeof(T) == 1, "elements too large"); + } + const T* data() const { return data_; } + size_t size() const { return size_; } +}; + +/** + * Takes a char array turns it into a StringBuffer of TextStr type. The length of the resulting + * StringBuffer is size - 1, effectively stripping the 0 character from the region being considered. + * If the terminating 0 shall not be stripped use text_keep_last. + */ +template <size_t size> +StringBuffer<char, TextStr> text(const char (&str)[size]) { + if (size > 0) return StringBuffer<char, TextStr>(str, size - 1); + return StringBuffer<char, TextStr>(str, size); +} + +/** + * As opposed to text(const char (&str)[size] this function does not strips the last character. + */ +template <size_t size> +StringBuffer<char, TextStr> text_keep_last(const char (&str)[size]) { + return StringBuffer<char, TextStr>(str, size); +} + +template <typename T> +auto getData(const T& v) -> decltype(v.data()) { + return v.data(); +} + +template <typename T> +auto getData(const T& v) -> decltype(v.c_str()) { + return v.c_str(); +} + +template <typename T> +auto text(const T& str) -> StringBuffer<std::decay_t<decltype(*getData(str))>, TextStr> { + return StringBuffer<std::decay_t<decltype(*getData(str))>, TextStr>(getData(str), str.size()); +} + +inline StringBuffer<char, TextStr> text(const char* str, size_t size) { + return StringBuffer<char, TextStr>(str, size); +} + +template <typename T, size_t size> +StringBuffer<T, ByteStr> bytes(const T (&str)[size]) { + return StringBuffer<T, ByteStr>(str, size); +} + +template <typename T> +StringBuffer<T, ByteStr> bytes(const T* str, size_t size) { + return StringBuffer<T, ByteStr>(str, size); +} + +template <typename T> +auto bytes(const T& str) -> StringBuffer<std::decay_t<decltype(*getData(str))>, ByteStr> { + return StringBuffer<std::decay_t<decltype(*getData(str))>, ByteStr>(getData(str), str.size()); +} + +template <typename... Elems> +struct Map; + +template <typename HeadKey, typename HeadValue, typename... Tail> +struct Map<MapElement<HeadKey, HeadValue>, Tail...> { + const MapElement<HeadKey, HeadValue>& head_; + Map<Tail...> tail_; + Map(const MapElement<HeadKey, HeadValue>& head, const Tail&... tail) + : head_(head), tail_(tail...) {} + constexpr size_t size() const { return sizeof...(Tail) + 1; }; +}; + +template <> +struct Map<> {}; + +template <typename... Keys, typename... Values> +Map<MapElement<Keys, Values>...> map(const MapElement<Keys, Values>&... elements) { + return Map<MapElement<Keys, Values>...>(elements...); +} + +template <typename... Elements> +Array<Elements...> arr(const Elements&... elements) { + return Array<Elements...>(elements...); +} + +template <typename Key, typename Value> +MapElement<Key, Value> pair(const Key& k, const Value& v) { + return MapElement<Key, Value>(k, v); +} + +template <size_t size> +struct getUnsignedType; + +template <> +struct getUnsignedType<sizeof(uint8_t)> { + typedef uint8_t type; +}; +template <> +struct getUnsignedType<sizeof(uint16_t)> { + typedef uint16_t type; +}; +template <> +struct getUnsignedType<sizeof(uint32_t)> { + typedef uint32_t type; +}; +template <> +struct getUnsignedType<sizeof(uint64_t)> { + typedef uint64_t type; +}; + +template <size_t size> +using Unsigned = typename getUnsignedType<size>::type; + +class WriteState { + public: + WriteState() : data_(nullptr), size_(0), error_(Error::OK) {} + WriteState(uint8_t* buffer, size_t size) : data_(buffer), size_(size), error_(Error::OK) {} + WriteState(uint8_t* buffer, size_t size, Error error) + : data_(buffer), size_(size), error_(error) {} + template <size_t size> + WriteState(uint8_t (&buffer)[size]) : data_(buffer), size_(size), error_(Error::OK) {} + + WriteState& operator++() { + if (size_) { + ++data_; + --size_; + } else { + error_ = Error::OUT_OF_DATA; + } + return *this; + } + WriteState& operator+=(size_t offset) { + if (offset > size_) { + error_ = Error::OUT_OF_DATA; + } else { + data_ += offset; + size_ -= offset; + } + return *this; + } + operator bool() const { return error_ == Error::OK; } + + uint8_t* data_; + size_t size_; + Error error_; +}; + +WriteState writeHeader(WriteState wState, Type type, const uint64_t value); +bool checkUTF8Copy(const char* begin, const char* const end, uint8_t* out); + +template <typename T> +WriteState writeNumber(WriteState wState, const T& v) { + if (!wState) return wState; + if (v >= 0) { + return writeHeader(wState, Type::NUMBER, v); + } else { + return writeHeader(wState, Type::NEGATIVE, UINT64_C(-1) - v); + } +} + +inline WriteState write(const WriteState& wState, const uint8_t& v) { + return writeNumber(wState, v); +} +inline WriteState write(const WriteState& wState, const int8_t& v) { + return writeNumber(wState, v); +} +inline WriteState write(const WriteState& wState, const uint16_t& v) { + return writeNumber(wState, v); +} +inline WriteState write(const WriteState& wState, const int16_t& v) { + return writeNumber(wState, v); +} +inline WriteState write(const WriteState& wState, const uint32_t& v) { + return writeNumber(wState, v); +} +inline WriteState write(const WriteState& wState, const int32_t& v) { + return writeNumber(wState, v); +} +inline WriteState write(const WriteState& wState, const uint64_t& v) { + return writeNumber(wState, v); +} +inline WriteState write(const WriteState& wState, const int64_t& v) { + return writeNumber(wState, v); +} + +template <typename T> +WriteState write(WriteState wState, const StringBuffer<T, TextStr>& v) { + wState = writeHeader(wState, Type::TEXT_STRING, v.size()); + uint8_t* buffer = wState.data_; + wState += v.size(); + if (!wState) return wState; + if (!checkUTF8Copy(v.data(), v.data() + v.size(), buffer)) { + wState.error_ = Error::MALFORMED_UTF8; + } + return wState; +} + +template <typename T> +WriteState write(WriteState wState, const StringBuffer<T, ByteStr>& v) { + wState = writeHeader(wState, Type::BYTE_STRING, v.size()); + uint8_t* buffer = wState.data_; + wState += v.size(); + if (!wState) return wState; + static_assert(sizeof(*v.data()) == 1, "elements too large"); + copy(v.data(), v.data() + v.size(), buffer); + return wState; +} + +template <template <typename...> class Arr> +WriteState writeArrayHelper(WriteState wState, const Arr<>&) { + return wState; +} + +template <template <typename...> class Arr, typename Head, typename... Tail> +WriteState writeArrayHelper(WriteState wState, const Arr<Head, Tail...>& arr) { + wState = write(wState, arr.head_); + return writeArrayHelper(wState, arr.tail_); +} + +template <typename... Elems> +WriteState write(WriteState wState, const Map<Elems...>& map) { + if (!wState) return wState; + wState = writeHeader(wState, Type::MAP, map.size()); + return writeArrayHelper(wState, map); +} + +template <typename... Elems> +WriteState write(WriteState wState, const Array<Elems...>& arr) { + if (!wState) return wState; + wState = writeHeader(wState, Type::ARRAY, arr.size()); + return writeArrayHelper(wState, arr); +} + +template <typename Key, typename Value> +WriteState write(WriteState wState, const MapElement<Key, Value>& element) { + if (!wState) return wState; + wState = write(wState, element.key_); + return write(wState, element.value_); +} + +template <typename Head, typename... Tail> +WriteState write(WriteState wState, const Head& head, const Tail&... tail) { + wState = write(wState, head); + return write(wState, tail...); +} + +} // namespace support +} // namespace confirmationui +} // namespace hardware +} // namespace android + +#endif // CONFIRMATIONUI_1_0_DEFAULT_CBOR_H_ diff --git a/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h b/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h new file mode 100644 index 000000000..d55143362 --- /dev/null +++ b/confirmationui/support/include/android/hardware/confirmationui/support/confirmationui_utils.h @@ -0,0 +1,213 @@ +/* +** +** Copyright 2017, 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 CONFIRMATIONUI_1_0_SUPPORT_INCLUDE_CONFIRMATIONUI_UTILS_H_ +#define CONFIRMATIONUI_1_0_SUPPORT_INCLUDE_CONFIRMATIONUI_UTILS_H_ + +#include <stddef.h> +#include <stdint.h> +#include <algorithm> +#include <initializer_list> +#include <type_traits> + +namespace android { +namespace hardware { +namespace confirmationui { +namespace support { + +/** + * This class wraps a (mostly return) value and stores whether or not the wrapped value is valid out + * of band. Note that if the wrapped value is a reference it is unsafe to access the value if + * !isOk(). If the wrapped type is a pointer or value and !isOk(), it is still safe to access the + * wrapped value. In this case the pointer will be NULL though, and the value will be default + * constructed. + */ +template <typename ValueT> +class NullOr { + template <typename T> + struct reference_initializer { + static T&& init() { return *static_cast<std::remove_reference_t<T>*>(nullptr); } + }; + template <typename T> + struct pointer_initializer { + static T init() { return nullptr; } + }; + template <typename T> + struct value_initializer { + static T init() { return T(); } + }; + template <typename T> + using initializer_t = + std::conditional_t<std::is_lvalue_reference<T>::value, reference_initializer<T>, + std::conditional_t<std::is_pointer<T>::value, pointer_initializer<T>, + value_initializer<T>>>; + + public: + NullOr() : value_(initializer_t<ValueT>::init()), null_(true) {} + NullOr(ValueT&& value) : value_(std::forward<ValueT>(value)), null_(false) {} + + bool isOk() const { return !null_; } + + const ValueT& value() const & { return value_; } + ValueT& value() & { return value_; } + ValueT&& value() && { return std::move(value_); } + + const std::remove_reference_t<ValueT>* operator->() const { return &value_; } + std::remove_reference_t<ValueT>* operator->() { return &value_; } + + private: + ValueT value_; + bool null_; +}; + +template <typename T, size_t elements> +class array { + using array_type = T[elements]; + + public: + array() : data_{} {} + array(const T (&data)[elements]) { std::copy(data, data + elements, data_); } + + T* data() { return data_; } + const T* data() const { return data_; } + constexpr size_t size() const { return elements; } + operator const array_type&() const { return data_; } + + T* begin() { return data_; } + T* end() { return data_ + elements; } + const T* begin() const { return data_; } + const T* end() const { return data_ + elements; } + + private: + array_type data_; +}; + +template <typename T> +auto bytes_cast(const T& v) -> const uint8_t (&)[sizeof(T)] { + return *reinterpret_cast<const uint8_t(*)[sizeof(T)]>(&v); +} +template <typename T> +auto bytes_cast(T& v) -> uint8_t (&)[sizeof(T)] { + return *reinterpret_cast<uint8_t(*)[sizeof(T)]>(&v); +} + +class ByteBufferProxy { + template <typename T> + struct has_data { + template <typename U> + static int f(const U*, const void*) { + return 0; + } + template <typename U> + static int* f(const U* u, decltype(u->data())) { + return nullptr; + } + static constexpr bool value = std::is_pointer<decltype(f((T*)nullptr, ""))>::value; + }; + + public: + template <typename T> + ByteBufferProxy(const T& buffer, decltype(buffer.data()) = nullptr) + : data_(reinterpret_cast<const uint8_t*>(buffer.data())), size_(buffer.size()) { + static_assert(sizeof(decltype(*buffer.data())) == 1, "elements to large"); + } + + // this overload kicks in for types that have .c_str() but not .data(), such as hidl_string. + // std::string has both so we need to explicitly disable this overload if .data() is present. + template <typename T> + ByteBufferProxy(const T& buffer, + std::enable_if_t<!has_data<T>::value, decltype(buffer.c_str())> = nullptr) + : data_(reinterpret_cast<const uint8_t*>(buffer.c_str())), size_(buffer.size()) { + static_assert(sizeof(decltype(*buffer.c_str())) == 1, "elements to large"); + } + + template <size_t size> + ByteBufferProxy(const char (&buffer)[size]) + : data_(reinterpret_cast<const uint8_t*>(buffer)), size_(size - 1) { + static_assert(size > 0, "even an empty string must be 0-terminated"); + } + + template <size_t size> + ByteBufferProxy(const uint8_t (&buffer)[size]) : data_(buffer), size_(size) {} + + ByteBufferProxy() : data_(nullptr), size_(0) {} + + const uint8_t* data() const { return data_; } + size_t size() const { return size_; } + + const uint8_t* begin() const { return data_; } + const uint8_t* end() const { return data_ + size_; } + + private: + const uint8_t* data_; + size_t size_; +}; + +/** + * Implementer are expected to provide an implementation with the following prototype: + * static NullOr<array<uint8_t, 32>> hmac256(const uint8_t key[32], + * std::initializer_list<ByteBufferProxy> buffers); + */ +template <typename Impl> +class HMac { + public: + template <typename... Data> + static NullOr<array<uint8_t, 32>> hmac256(const uint8_t key[32], const Data&... data) { + return Impl::hmac256(key, {data...}); + } +}; + +bool operator==(const ByteBufferProxy& lhs, const ByteBufferProxy& rhs); + +template <typename IntType, uint32_t byteOrder> +struct choose_hton; + +template <typename IntType> +struct choose_hton<IntType, __ORDER_LITTLE_ENDIAN__> { + inline static IntType hton(const IntType& value) { + IntType result = {}; + const unsigned char* inbytes = reinterpret_cast<const unsigned char*>(&value); + unsigned char* outbytes = reinterpret_cast<unsigned char*>(&result); + for (int i = sizeof(IntType) - 1; i >= 0; --i) { + *(outbytes++) = inbytes[i]; + } + return result; + } +}; + +template <typename IntType> +struct choose_hton<IntType, __ORDER_BIG_ENDIAN__> { + inline static IntType hton(const IntType& value) { return value; } +}; + +template <typename IntType> +inline IntType hton(const IntType& value) { + return choose_hton<IntType, __BYTE_ORDER__>::hton(value); +} + +template <typename IntType> +inline IntType ntoh(const IntType& value) { + // same operation as hton + return choose_hton<IntType, __BYTE_ORDER__>::hton(value); +} + +} // namespace support +} // namespace confirmationui +} // namespace hardware +} // namespace android + +#endif // CONFIRMATIONUI_1_0_SUPPORT_INCLUDE_CONFIRMATIONUI_UTILS_H_ diff --git a/confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h b/confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h new file mode 100644 index 000000000..0d0359177 --- /dev/null +++ b/confirmationui/support/include/android/hardware/confirmationui/support/msg_formatting.h @@ -0,0 +1,471 @@ +/* +** +** Copyright 2017, 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 CONFIRMATIONUI_SUPPORT_INCLUDE_ANDROID_HARDWARE_CONFIRMATIONUI_SUPPORT_MSG_FORMATTING_H_ +#define CONFIRMATIONUI_SUPPORT_INCLUDE_ANDROID_HARDWARE_CONFIRMATIONUI_SUPPORT_MSG_FORMATTING_H_ + +#include <android/hardware/confirmationui/1.0/types.h> +#include <android/hardware/keymaster/4.0/types.h> +#include <stddef.h> +#include <stdint.h> +#include <algorithm> +#include <tuple> +#include <type_traits> + +#include <android/hardware/confirmationui/support/confirmationui_utils.h> + +namespace android { +namespace hardware { +namespace confirmationui { +namespace support { + +template <size_t... I> +class IntegerSequence {}; + +namespace integer_sequence { + +template <typename Lhs, typename Rhs> +struct conc {}; + +template <size_t... ILhs, size_t... IRhs> +struct conc<IntegerSequence<ILhs...>, IntegerSequence<IRhs...>> { + using type = IntegerSequence<ILhs..., IRhs...>; +}; + +template <typename Lhs, typename Rhs> +using conc_t = typename conc<Lhs, Rhs>::type; + +template <size_t... n> +struct make {}; + +template <size_t n> +struct make<n> { + using type = conc_t<typename make<n - 1>::type, IntegerSequence<n - 1>>; +}; +template <size_t start, size_t n> +struct make<start, n> { + using type = conc_t<typename make<start, n - 1>::type, IntegerSequence<start + n - 1>>; +}; + +template <size_t start> +struct make<start, start> { + using type = IntegerSequence<start>; +}; + +template <> +struct make<0> { + using type = IntegerSequence<>; +}; + +template <size_t... n> +using make_t = typename make<n...>::type; + +} // namespace integer_sequence + +template <size_t... idx, typename... T> +std::tuple<std::remove_reference_t<T>&&...> tuple_move_helper(IntegerSequence<idx...>, + std::tuple<T...>&& t) { + return {std::move(std::get<idx>(t))...}; +} + +template <typename... T> +std::tuple<std::remove_reference_t<T>&&...> tuple_move(std::tuple<T...>&& t) { + return tuple_move_helper(integer_sequence::make_t<sizeof...(T)>(), std::move(t)); +} + +template <typename... T> +std::tuple<std::remove_reference_t<T>&&...> tuple_move(std::tuple<T...>& t) { + return tuple_move_helper(integer_sequence::make_t<sizeof...(T)>(), std::move(t)); +} + +using ::android::hardware::confirmationui::V1_0::ResponseCode; +using ::android::hardware::confirmationui::V1_0::UIOption; +using ::android::hardware::keymaster::V4_0::HardwareAuthToken; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +template <typename... fields> +class Message {}; + +enum class Command : uint32_t { + PromptUserConfirmation, + DeliverSecureInputEvent, + Abort, +}; + +template <Command cmd> +struct Cmd {}; + +#define DECLARE_COMMAND(cmd) using cmd##_t = Cmd<Command::cmd> + +DECLARE_COMMAND(PromptUserConfirmation); +DECLARE_COMMAND(DeliverSecureInputEvent); +DECLARE_COMMAND(Abort); + +using PromptUserConfirmationMsg = Message<PromptUserConfirmation_t, hidl_string, hidl_vec<uint8_t>, + hidl_string, hidl_vec<UIOption>>; +using PromptUserConfirmationResponse = Message<ResponseCode>; +using DeliverSecureInputEventMsg = Message<DeliverSecureInputEvent_t, HardwareAuthToken>; +using DeliverSecureInputEventRespose = Message<ResponseCode>; +using AbortMsg = Message<Abort_t>; +using ResultMsg = Message<ResponseCode, hidl_vec<uint8_t>, hidl_vec<uint8_t>>; + +template <typename T> +struct StreamState { + using ptr_t = volatile T*; + volatile T* pos_; + size_t bytes_left_; + bool good_; + template <size_t size> + StreamState(T (&buffer)[size]) : pos_(buffer), bytes_left_(size), good_(size > 0) {} + StreamState(T* buffer, size_t size) : pos_(buffer), bytes_left_(size), good_(size > 0) {} + StreamState() : pos_(nullptr), bytes_left_(0), good_(false) {} + StreamState& operator++() { + if (good_ && bytes_left_) { + ++pos_; + --bytes_left_; + } else { + good_ = false; + } + return *this; + } + StreamState& operator+=(size_t offset) { + if (!good_ || offset > bytes_left_) { + good_ = false; + } else { + pos_ += offset; + bytes_left_ -= offset; + } + return *this; + } + operator bool() const { return good_; } + volatile T* pos() const { return pos_; }; +}; + +using WriteStream = StreamState<uint8_t>; +using ReadStream = StreamState<const uint8_t>; + +inline void zero(volatile uint8_t* begin, const volatile uint8_t* end) { + while (begin != end) { + *begin++ = 0xaa; + } +} +inline void zero(const volatile uint8_t*, const volatile uint8_t*) {} +// This odd alignment function aligns the stream position to a 4byte and never 8byte boundary +// It is to accommodate the 4 byte size field which is then followed by 8byte alligned data. +template <typename T> +StreamState<T> unalign(StreamState<T> s) { + uint8_t unalignment = uintptr_t(s.pos_) & 0x3; + auto pos = s.pos_; + if (unalignment) { + s += 4 - unalignment; + } + // now s.pos_ is aligned on a 4byte boundary + if ((uintptr_t(s.pos_) & 0x4) == 0) { + // if we are 8byte aligned add 4 + s += 4; + } + // zero out the gaps when writing + zero(pos, s.pos_); + return s; +} + +inline WriteStream write(WriteStream out, const uint8_t* buffer, size_t size) { + auto pos = out.pos(); + uint32_t v = size; + out += 4 + size; + if (out) { + if (size != v) { + out.good_ = false; + return out; + } + auto& s = bytes_cast(v); + pos = std::copy(s, s + 4, pos); + std::copy(buffer, buffer + size, pos); + } + return out; +} +template <size_t size> +WriteStream write(WriteStream out, const uint8_t (&v)[size]) { + return write(out, v, size); +} + +inline std::tuple<ReadStream, ReadStream::ptr_t, size_t> read(ReadStream in) { + auto pos = in.pos(); + in += 4; + if (!in) return {in, nullptr, 0}; + uint32_t size; + std::copy(pos, pos + 4, bytes_cast(size)); + pos = in.pos(); + in += size; + if (!in) return {in, nullptr, 0}; + return {in, pos, size}; +} + +template <typename T> +std::tuple<ReadStream, T> readSimpleType(ReadStream in) { + T result; + ReadStream::ptr_t pos = nullptr; + size_t read_size = 0; + std::tie(in, pos, read_size) = read(in); + if (!in || read_size != sizeof(T)) { + in.good_ = false; + return {in, {}}; + } + std::copy(pos, pos + sizeof(T), bytes_cast(result)); + return {in, std::move(result)}; +} + +template <typename T> +std::tuple<ReadStream, hidl_vec<T>> readSimpleHidlVecInPlace(ReadStream in) { + std::tuple<ReadStream, hidl_vec<T>> result; + ReadStream::ptr_t pos = nullptr; + size_t read_size = 0; + std::tie(std::get<0>(result), pos, read_size) = read(in); + if (!std::get<0>(result) || read_size % sizeof(T)) { + std::get<0>(result).good_ = false; + return result; + } + std::get<1>(result).setToExternal(reinterpret_cast<T*>(const_cast<uint8_t*>(pos)), + read_size / sizeof(T)); + return result; +} + +template <typename T> +WriteStream writeSimpleHidlVec(WriteStream out, const hidl_vec<T>& vec) { + return write(out, reinterpret_cast<const uint8_t*>(vec.data()), vec.size() * sizeof(T)); +} + +// HardwareAuthToken +constexpr size_t hatSizeNoMac() { + HardwareAuthToken* hat = nullptr; + return sizeof hat->challenge + sizeof hat->userId + sizeof hat->authenticatorId + + sizeof hat->authenticatorType + sizeof hat->timestamp; +} + +template <typename T> +inline volatile const uint8_t* copyField(T& field, volatile const uint8_t*(&pos)) { + auto& s = bytes_cast(field); + std::copy(pos, pos + sizeof(T), s); + return pos + sizeof(T); +} +inline std::tuple<ReadStream, HardwareAuthToken> read(Message<HardwareAuthToken>, ReadStream in_) { + std::tuple<ReadStream, HardwareAuthToken> result; + ReadStream& in = std::get<0>(result) = in_; + auto& hat = std::get<1>(result); + constexpr size_t hatSize = hatSizeNoMac(); + ReadStream::ptr_t pos = nullptr; + size_t read_size = 0; + std::tie(in, pos, read_size) = read(in); + if (!in || read_size != hatSize) { + in.good_ = false; + return result; + } + pos = copyField(hat.challenge, pos); + pos = copyField(hat.userId, pos); + pos = copyField(hat.authenticatorId, pos); + pos = copyField(hat.authenticatorType, pos); + pos = copyField(hat.timestamp, pos); + std::tie(in, hat.mac) = readSimpleHidlVecInPlace<uint8_t>(in); + return result; +} + +template <typename T> +inline volatile uint8_t* copyField(const T& field, volatile uint8_t*(&pos)) { + auto& s = bytes_cast(field); + return std::copy(s, &s[sizeof(T)], pos); +} + +inline WriteStream write(WriteStream out, const HardwareAuthToken& v) { + auto pos = out.pos(); + uint32_t size_field = hatSizeNoMac(); + out += 4 + size_field; + if (!out) return out; + pos = copyField(size_field, pos); + pos = copyField(v.challenge, pos); + pos = copyField(v.userId, pos); + pos = copyField(v.authenticatorId, pos); + pos = copyField(v.authenticatorType, pos); + pos = copyField(v.timestamp, pos); + return writeSimpleHidlVec(out, v.mac); +} + +// ResponseCode +inline std::tuple<ReadStream, ResponseCode> read(Message<ResponseCode>, ReadStream in) { + return readSimpleType<ResponseCode>(in); +} +inline WriteStream write(WriteStream out, const ResponseCode& v) { + return write(out, bytes_cast(v)); +} + +// hidl_vec<uint8_t> +inline std::tuple<ReadStream, hidl_vec<uint8_t>> read(Message<hidl_vec<uint8_t>>, ReadStream in) { + return readSimpleHidlVecInPlace<uint8_t>(in); +} +inline WriteStream write(WriteStream out, const hidl_vec<uint8_t>& v) { + return writeSimpleHidlVec(out, v); +} + +// hidl_vec<UIOption> +inline std::tuple<ReadStream, hidl_vec<UIOption>> read(Message<hidl_vec<UIOption>>, ReadStream in) { + in = unalign(in); + return readSimpleHidlVecInPlace<UIOption>(in); +} +inline WriteStream write(WriteStream out, const hidl_vec<UIOption>& v) { + out = unalign(out); + return writeSimpleHidlVec(out, v); +} + +// hidl_string +inline std::tuple<ReadStream, hidl_string> read(Message<hidl_string>, ReadStream in) { + std::tuple<ReadStream, hidl_string> result; + ReadStream& in_ = std::get<0>(result); + hidl_string& result_ = std::get<1>(result); + ReadStream::ptr_t pos = nullptr; + size_t read_size = 0; + std::tie(in_, pos, read_size) = read(in); + auto terminating_zero = in_.pos(); + ++in_; // skip the terminating zero. Does nothing if the stream was already bad + if (!in_) return result; + if (*terminating_zero) { + in_.good_ = false; + return result; + } + result_.setToExternal(reinterpret_cast<const char*>(const_cast<const uint8_t*>(pos)), + read_size); + return result; +} +inline WriteStream write(WriteStream out, const hidl_string& v) { + out = write(out, reinterpret_cast<const uint8_t*>(v.c_str()), v.size()); + auto terminating_zero = out.pos(); + ++out; + if (out) { + *terminating_zero = 0; + } + return out; +} + +inline WriteStream write(WriteStream out, Command cmd) { + volatile Command* pos = reinterpret_cast<volatile Command*>(out.pos_); + out += sizeof(Command); + if (out) { + *pos = cmd; + } + return out; +} +template <Command cmd> +WriteStream write(WriteStream out, Cmd<cmd>) { + return write(out, cmd); +} + +inline std::tuple<ReadStream, bool> read(ReadStream in, Command cmd) { + volatile const Command* pos = reinterpret_cast<volatile const Command*>(in.pos_); + in += sizeof(Command); + if (!in) return {in, false}; + return {in, *pos == cmd}; +} + +template <Command cmd> +std::tuple<ReadStream, bool> read(Message<Cmd<cmd>>, ReadStream in) { + return read(in, cmd); +} + +inline WriteStream write(Message<>, WriteStream out) { + return out; +} + +template <typename Head, typename... Tail> +WriteStream write(Message<Head, Tail...>, WriteStream out, const Head& head, const Tail&... tail) { + out = write(out, head); + return write(Message<Tail...>(), out, tail...); +} + +template <Command cmd, typename... Tail> +WriteStream write(Message<Cmd<cmd>, Tail...>, WriteStream out, const Tail&... tail) { + out = write(out, cmd); + return write(Message<Tail...>(), out, tail...); +} + +template <Command cmd, typename HEAD, typename... Tail> +std::tuple<ReadStream, bool, HEAD, Tail...> read(Message<Cmd<cmd>, HEAD, Tail...>, ReadStream in) { + bool command_matches; + std::tie(in, command_matches) = read(in, cmd); + if (!command_matches) return {in, false, HEAD(), Tail()...}; + + return {in, true, + [&]() -> HEAD { + HEAD result; + std::tie(in, result) = read(Message<HEAD>(), in); + return result; + }(), + [&]() -> Tail { + Tail result; + std::tie(in, result) = read(Message<Tail>(), in); + return result; + }()...}; +} + +template <typename... Msg> +std::tuple<ReadStream, Msg...> read(Message<Msg...>, ReadStream in) { + return {in, [&in]() -> Msg { + Msg result; + std::tie(in, result) = read(Message<Msg>(), in); + return result; + }()...}; +} + +template <typename T> +struct msg2tuple {}; + +template <typename... T> +struct msg2tuple<Message<T...>> { + using type = std::tuple<T...>; +}; +template <Command cmd, typename... T> +struct msg2tuple<Message<Cmd<cmd>, T...>> { + using type = std::tuple<T...>; +}; + +template <typename T> +using msg2tuple_t = typename msg2tuple<T>::type; + +template <size_t... idx, typename HEAD, typename... T> +std::tuple<T&&...> tuple_tail(IntegerSequence<idx...>, std::tuple<HEAD, T...>&& t) { + return {std::move(std::get<idx>(t))...}; +} + +template <size_t... idx, typename HEAD, typename... T> +std::tuple<const T&...> tuple_tail(IntegerSequence<idx...>, const std::tuple<HEAD, T...>& t) { + return {std::get<idx>(t)...}; +} + +template <typename HEAD, typename... Tail> +std::tuple<Tail&&...> tuple_tail(std::tuple<HEAD, Tail...>&& t) { + return tuple_tail(integer_sequence::make_t<1, sizeof...(Tail)>(), std::move(t)); +} + +template <typename HEAD, typename... Tail> +std::tuple<const Tail&...> tuple_tail(const std::tuple<HEAD, Tail...>& t) { + return tuple_tail(integer_sequence::make_t<1, sizeof...(Tail)>(), t); +} + +} // namespace support +} // namespace confirmationui +} // namespace hardware +} // namespace android + +#endif // CONFIRMATIONUI_SUPPORT_INCLUDE_ANDROID_HARDWARE_CONFIRMATIONUI_SUPPORT_MSG_FORMATTING_H_ diff --git a/confirmationui/support/src/cbor.cpp b/confirmationui/support/src/cbor.cpp new file mode 100644 index 000000000..e7ea164d9 --- /dev/null +++ b/confirmationui/support/src/cbor.cpp @@ -0,0 +1,111 @@ +/* +** +** Copyright 2017, 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/hardware/confirmationui/support/cbor.h> + +namespace android { +namespace hardware { +namespace confirmationui { +namespace support { +namespace { + +inline uint8_t getByte(const uint64_t& v, const uint8_t index) { + return v >> (index * 8); +} + +WriteState writeBytes(WriteState state, uint64_t value, uint8_t size) { + auto pos = state.data_; + if (!(state += size)) return state; + switch (size) { + case 8: + *pos++ = getByte(value, 7); + *pos++ = getByte(value, 6); + *pos++ = getByte(value, 5); + *pos++ = getByte(value, 4); + case 4: + *pos++ = getByte(value, 3); + *pos++ = getByte(value, 2); + case 2: + *pos++ = getByte(value, 1); + case 1: + *pos++ = value; + break; + default: + state.error_ = Error::MALFORMED; + } + return state; +} + +} // anonymous namespace + +WriteState writeHeader(WriteState wState, Type type, const uint64_t value) { + if (!wState) return wState; + uint8_t& header = *wState.data_; + if (!++wState) return wState; + header = static_cast<uint8_t>(type) << 5; + if (value < 24) { + header |= static_cast<uint8_t>(value); + } else if (value < 0x100) { + header |= 24; + wState = writeBytes(wState, value, 1); + } else if (value < 0x10000) { + header |= 25; + wState = writeBytes(wState, value, 2); + } else if (value < 0x100000000) { + header |= 26; + wState = writeBytes(wState, value, 4); + } else { + header |= 27; + wState = writeBytes(wState, value, 8); + } + return wState; +} + +bool checkUTF8Copy(const char* begin, const char* const end, uint8_t* out) { + uint32_t multi_byte_length = 0; + while (begin != end) { + if (multi_byte_length) { + // parsing multi byte character - must start with 10xxxxxx + --multi_byte_length; + if ((*begin & 0xc0) != 0x80) return false; + } else if (!((*begin) & 0x80)) { + // 7bit character -> nothing to be done + } else { + // msb is set and we were not parsing a multi byte character + // so this must be a header byte + char c = *begin << 1; + while (c & 0x80) { + ++multi_byte_length; + c <<= 1; + } + // headers of the form 10xxxxxx are not allowed + if (multi_byte_length < 1) return false; + // chars longer than 4 bytes are not allowed (multi_byte_length does not count the + // header thus > 3 + if (multi_byte_length > 3) return false; + } + if (out) *out++ = *reinterpret_cast<const uint8_t*>(begin++); + } + // if the string ends in the middle of a multi byte char it is invalid + if (multi_byte_length) return false; + return true; +} + +} // namespace support +} // namespace confirmationui +} // namespace hardware +} // namespace android diff --git a/confirmationui/support/src/confirmationui_utils.cpp b/confirmationui/support/src/confirmationui_utils.cpp new file mode 100644 index 000000000..708f0d56a --- /dev/null +++ b/confirmationui/support/src/confirmationui_utils.cpp @@ -0,0 +1,39 @@ +/* +** +** Copyright 2017, 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/hardware/confirmationui/support/confirmationui_utils.h> + +namespace android { +namespace hardware { +namespace confirmationui { +namespace support { + +bool operator==(const ByteBufferProxy& lhs, const ByteBufferProxy& rhs) { + if (lhs.size() == rhs.size()) { + auto lhsi = lhs.begin(); + auto rhsi = rhs.begin(); + while (lhsi != lhs.end()) { + if (*lhsi++ != *rhsi++) return false; + } + } + return true; +} + +} // namespace support +} // namespace confirmationui +} // namespace hardware +} // namespace android diff --git a/confirmationui/support/test/android_cbor_test.cpp b/confirmationui/support/test/android_cbor_test.cpp new file mode 100644 index 000000000..4a5a3628c --- /dev/null +++ b/confirmationui/support/test/android_cbor_test.cpp @@ -0,0 +1,197 @@ +/* +** +** Copyright 2017, 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/hardware/confirmationui/support/cbor.h> + +#include <cstddef> +#include <cstdint> +#include <iomanip> +#include <iostream> + +#include <gtest/gtest.h> + +using namespace android::hardware::confirmationui::support; + +uint8_t testVector[] = { + 0xA4, 0x63, 0x6B, 0x65, 0x79, 0x65, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x63, 0x6B, 0x65, 0x79, 0x4D, + 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x31, 0x30, 0x00, 0x04, 0x07, 0x1B, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x07, 0x1A, 0xFD, 0x49, 0x8C, 0xFF, + 0xFF, 0x82, 0x69, 0xE2, 0x99, 0xA8, 0xE2, 0x9A, 0x96, 0xE2, 0xB6, 0x96, 0x59, 0x01, 0x91, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00, +}; + +// 400 'a's and a '\0' +constexpr char fourHundredAs[] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaa"; + +WriteState writeTest(WriteState state) { + return write(state, // + map( // + pair(text("key"), text("value")), // + pair(text("key"), bytes("100101010010")), // + pair(4, 7), // + pair((UINT64_C(1) << 62), INT64_C(-2000000000000000)) // + ), // + arr(text("♨⚖ⶖ"), bytes(fourHundredAs))); +} + +TEST(Cbor, FeatureTest) { + uint8_t buffer[0x1000]; + WriteState state(buffer); + state = writeTest(state); + ASSERT_EQ(sizeof(testVector), size_t(state.data_ - buffer)); + ASSERT_EQ(Error::OK, state.error_); + ASSERT_EQ(0, memcmp(buffer, testVector, sizeof(testVector))); +} + +// Test if in all write cases an out of data error is correctly propagated and we don't +// write beyond the end of the buffer. +TEST(Cbor, BufferTooShort) { + uint8_t buffer[0x1000]; + for (size_t s = 1; s < sizeof(testVector); ++s) { + memset(buffer, 0x22, 0x1000); // 0x22 is not in the testVector + WriteState state(buffer, s); + state = writeTest(state); + for (size_t t = s; t < 0x1000; ++t) { + ASSERT_EQ(0x22, buffer[t]); // check if a canary has been killed + } + ASSERT_EQ(Error::OUT_OF_DATA, state.error_); + } +} + +TEST(Cbor, MalformedUTF8Test_Stray) { + uint8_t buffer[20]; + WriteState state(buffer); + char malformed[] = {char(0x80), 0}; + state = write(state, text(malformed)); + ASSERT_EQ(Error::MALFORMED_UTF8, state.error_); +} + +TEST(Cbor, MalformendUTF8Test_StringEndsMidMultiByte) { + uint8_t buffer[20]; + WriteState state(buffer); + char malformed[] = {char(0xc0), 0}; + state = write(state, text(malformed)); + ASSERT_EQ(Error::MALFORMED_UTF8, state.error_); +} + +TEST(Cbor, UTF8Test_TwoBytes) { + uint8_t buffer[20]; + WriteState state(buffer); + char neat[] = {char(0xc3), char(0x82), 0}; + state = write(state, text(neat)); + ASSERT_EQ(Error::OK, state.error_); +} + +TEST(Cbor, UTF8Test_ThreeBytes) { + uint8_t buffer[20]; + WriteState state(buffer); + char neat[] = {char(0xe3), char(0x82), char(0x82), 0}; + state = write(state, text(neat)); + ASSERT_EQ(Error::OK, state.error_); +} + +TEST(Cbor, UTF8Test_FourBytes) { + uint8_t buffer[20]; + WriteState state(buffer); + char neat[] = {char(0xf3), char(0x82), char(0x82), char(0x82), 0}; + state = write(state, text(neat)); + ASSERT_EQ(Error::OK, state.error_); +} + +TEST(Cbor, MalformendUTF8Test_CharacterTooLong) { + uint8_t buffer[20]; + WriteState state(buffer); + char malformed[] = {char(0xf8), char(0x82), char(0x82), char(0x82), char(0x82), 0}; + state = write(state, text(malformed)); + ASSERT_EQ(Error::MALFORMED_UTF8, state.error_); +} + +TEST(Cbor, MalformendUTF8Test_StringEndsMidMultiByte2) { + uint8_t buffer[20]; + WriteState state(buffer); + char malformed[] = {char(0xc0), char(0x82), char(0x83), 0}; + state = write(state, text(malformed)); + ASSERT_EQ(Error::MALFORMED_UTF8, state.error_); +} + +TEST(Cbor, MinimalViableHeaderSizeTest) { + uint8_t buffer[20]; + WriteState state(buffer); + state = writeHeader(state, Type::NUMBER, 23); + ASSERT_EQ(state.data_ - buffer, 1); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 24); + ASSERT_EQ(state.data_ - buffer, 2); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 0xff); + ASSERT_EQ(state.data_ - buffer, 2); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 0x100); + ASSERT_EQ(state.data_ - buffer, 3); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 0xffff); + ASSERT_EQ(state.data_ - buffer, 3); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 0x10000); + ASSERT_EQ(state.data_ - buffer, 5); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 0xffffffff); + ASSERT_EQ(state.data_ - buffer, 5); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 0x100000000); + ASSERT_EQ(state.data_ - buffer, 9); + + state = WriteState(buffer); + state = writeHeader(state, Type::NUMBER, 0xffffffffffffffff); + ASSERT_EQ(state.data_ - buffer, 9); +} diff --git a/confirmationui/support/test/gtest_main.cpp b/confirmationui/support/test/gtest_main.cpp new file mode 100644 index 000000000..43fc5a343 --- /dev/null +++ b/confirmationui/support/test/gtest_main.cpp @@ -0,0 +1,23 @@ +/* +** +** Copyright 2017, 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 <gtest/gtest.h> + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/confirmationui/support/test/msg_formatting_test.cpp b/confirmationui/support/test/msg_formatting_test.cpp new file mode 100644 index 000000000..90ed84cfa --- /dev/null +++ b/confirmationui/support/test/msg_formatting_test.cpp @@ -0,0 +1,128 @@ +/* +** +** Copyright 2017, 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 <stddef.h> +#include <stdint.h> +#include <iomanip> +#include <iostream> +#include <string> + +#include <android/hardware/confirmationui/support/msg_formatting.h> +#include <gtest/gtest.h> + +using android::hardware::confirmationui::support::Message; +using android::hardware::confirmationui::support::WriteStream; +using android::hardware::confirmationui::support::ReadStream; +using android::hardware::confirmationui::support::PromptUserConfirmationMsg; +using android::hardware::confirmationui::support::write; +using ::android::hardware::confirmationui::V1_0::UIOption; +using ::android::hardware::keymaster::V4_0::HardwareAuthToken; +using ::android::hardware::keymaster::V4_0::HardwareAuthenticatorType; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +#ifdef DEBUG_MSG_FORMATTING +namespace { + +char nibble2hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +std::ostream& hexdump(std::ostream& out, const uint8_t* data, size_t size) { + for (size_t i = 0; i < size; ++i) { + uint8_t byte = data[i]; + out << (nibble2hex[0x0F & (byte >> 4)]); + out << (nibble2hex[0x0F & byte]); + switch (i & 0xf) { + case 0xf: + out << "\n"; + break; + case 7: + out << " "; + break; + default: + out << " "; + break; + } + } + return out; +} + +} // namespace +#endif + +TEST(MsgFormattingTest, FeatureTest) { + uint8_t buffer[0x1000]; + + WriteStream out(buffer); + out = unalign(out); + out += 4; + auto begin = out.pos(); + out = write( + PromptUserConfirmationMsg(), out, hidl_string("Do you?"), + hidl_vec<uint8_t>{0x01, 0x02, 0x03}, hidl_string("en"), + hidl_vec<UIOption>{UIOption::AccessibilityInverted, UIOption::AccessibilityMagnified}); + + ReadStream in(buffer); + in = unalign(in); + in += 4; + hidl_string prompt; + hidl_vec<uint8_t> extra; + hidl_string locale; + hidl_vec<UIOption> uiOpts; + bool command_matches; + std::tie(in, command_matches, prompt, extra, locale, uiOpts) = + read(PromptUserConfirmationMsg(), in); + ASSERT_TRUE(in); + ASSERT_TRUE(command_matches); + ASSERT_EQ(hidl_string("Do you?"), prompt); + ASSERT_EQ((hidl_vec<uint8_t>{0x01, 0x02, 0x03}), extra); + ASSERT_EQ(hidl_string("en"), locale); + ASSERT_EQ( + (hidl_vec<UIOption>{UIOption::AccessibilityInverted, UIOption::AccessibilityMagnified}), + uiOpts); + +#ifdef DEBUG_MSG_FORMATTING + hexdump(std::cout, buffer, 100) << std::endl; +#endif + + // The following assertions check that the hidl_[vec|string] types are in fact read in place, + // and no copying occurs. Copying results in heap allocation which we intend to avoid. + ASSERT_EQ(8, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(prompt.c_str())) - begin); + ASSERT_EQ(20, extra.data() - begin); + ASSERT_EQ(27, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(locale.c_str())) - begin); + ASSERT_EQ(40, reinterpret_cast<uint8_t*>(uiOpts.data()) - begin); +} + +TEST(MsgFormattingTest, HardwareAuthTokenTest) { + uint8_t buffer[0x1000]; + + HardwareAuthToken expected, actual; + expected.authenticatorId = 0xa1a3a4a5a6a7a8; + expected.authenticatorType = HardwareAuthenticatorType::NONE; + expected.challenge = 0xb1b2b3b4b5b6b7b8; + expected.userId = 0x1122334455667788; + expected.timestamp = 0xf1f2f3f4f5f6f7f8; + expected.mac = + hidl_vec<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}; + + WriteStream out(buffer); + out = write(Message<HardwareAuthToken>(), out, expected); + ReadStream in(buffer); + std::tie(in, actual) = read(Message<HardwareAuthToken>(), in); + ASSERT_EQ(expected, actual); +} |