From e66bab4fec5b34a5284577e31084fc2654f093d4 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Thu, 2 Oct 2025 19:49:05 +0800 Subject: [PATCH 01/46] Initial Signed-off-by: qwq233 --- .gitignore | 0 LICENSE-2 | 16 + LICENSE.md | 660 +++++++ README.md | 71 + libs/rust/.gitignore | 20 + libs/rust/Cargo.toml | 66 + libs/rust/boringssl/Cargo.toml | 24 + libs/rust/boringssl/src/aes.rs | 286 +++ libs/rust/boringssl/src/aes_cmac.rs | 117 ++ libs/rust/boringssl/src/des.rs | 90 + libs/rust/boringssl/src/ec.rs | 449 +++++ libs/rust/boringssl/src/eq.rs | 29 + libs/rust/boringssl/src/err.rs | 127 ++ libs/rust/boringssl/src/hmac.rs | 132 ++ libs/rust/boringssl/src/lib.rs | 160 ++ libs/rust/boringssl/src/rng.rs | 44 + libs/rust/boringssl/src/rsa.rs | 348 ++++ libs/rust/boringssl/src/sha256.rs | 27 + libs/rust/boringssl/src/tests.rs | 67 + libs/rust/boringssl/src/types.rs | 50 + libs/rust/build.rs | 24 + libs/rust/common/Cargo.toml | 28 + libs/rust/common/fuzz/.gitignore | 3 + libs/rust/common/fuzz/Cargo.toml | 30 + libs/rust/common/fuzz/fuzz_targets/keyblob.rs | 24 + libs/rust/common/generated.cddl | 638 +++++++ libs/rust/common/src/bin/cddl-dump.rs | 136 ++ libs/rust/common/src/bin/keyblob-cddl-dump.rs | 161 ++ libs/rust/common/src/crypto.rs | 571 ++++++ libs/rust/common/src/crypto/aes.rs | 238 +++ libs/rust/common/src/crypto/des.rs | 117 ++ libs/rust/common/src/crypto/ec.rs | 673 +++++++ libs/rust/common/src/crypto/hmac.rs | 69 + libs/rust/common/src/crypto/rsa.rs | 249 +++ libs/rust/common/src/crypto/traits.rs | 742 ++++++++ libs/rust/common/src/keyblob.rs | 474 +++++ libs/rust/common/src/keyblob/keyblob.cddl | 274 +++ libs/rust/common/src/keyblob/legacy.rs | 293 +++ libs/rust/common/src/keyblob/legacy/tests.rs | 281 +++ libs/rust/common/src/keyblob/sdd_mem.rs | 103 ++ libs/rust/common/src/keyblob/tests.rs | 90 + libs/rust/common/src/lib.rs | 215 +++ libs/rust/common/src/tag.rs | 1429 +++++++++++++++ libs/rust/common/src/tag/info.rs | 1188 ++++++++++++ libs/rust/common/src/tag/info/tests.rs | 93 + libs/rust/common/src/tag/legacy.rs | 600 ++++++ libs/rust/common/src/tag/tests.rs | 138 ++ libs/rust/derive/Cargo.toml | 22 + libs/rust/derive/src/lib.rs | 597 ++++++ libs/rust/derive/tests/integration_test.rs | 65 + libs/rust/hal/src/env.rs | 204 +++ libs/rust/hal/src/hal.rs | 772 ++++++++ libs/rust/hal/src/hal/tests.rs | 165 ++ libs/rust/hal/src/keymint.rs | 500 +++++ libs/rust/hal/src/lib.rs | 318 ++++ libs/rust/hal/src/rpc.rs | 103 ++ libs/rust/hal/src/secureclock.rs | 58 + libs/rust/hal/src/sharedsecret.rs | 67 + libs/rust/hal/src/tests.rs | 178 ++ libs/rust/proto/storage.proto | 27 + libs/rust/scripts/cddl-gen | 6 + libs/rust/src/.gitignore | 1 + libs/rust/src/attest.rs | 425 +++++ libs/rust/src/clock.rs | 45 + libs/rust/src/lib.rs | 0 libs/rust/src/logging.rs | 18 + libs/rust/src/macros.rs | 31 + libs/rust/src/main.rs | 209 +++ libs/rust/src/rpc.rs | 234 +++ libs/rust/src/sdd.rs | 244 +++ libs/rust/src/soft.rs | 67 + libs/rust/ta/Cargo.toml | 31 + libs/rust/ta/fuzz/.gitignore | 4 + libs/rust/ta/fuzz/Cargo.toml | 32 + .../ta/fuzz/fuzz_targets/keydescription.rs | 9 + libs/rust/ta/src/cert.rs | 1602 +++++++++++++++++ libs/rust/ta/src/clock.rs | 49 + libs/rust/ta/src/device.rs | 398 ++++ libs/rust/ta/src/keys.rs | 819 +++++++++ libs/rust/ta/src/lib.rs | 1327 ++++++++++++++ libs/rust/ta/src/operation.rs | 869 +++++++++ libs/rust/ta/src/rkp.rs | 335 ++++ libs/rust/ta/src/secret.rs | 99 + libs/rust/ta/src/tests.rs | 344 ++++ libs/rust/tests/Cargo.toml | 23 + libs/rust/tests/src/bin/auth-keyblob-parse.rs | 188 ++ .../tests/src/bin/encrypted-keyblob-parse.rs | 82 + libs/rust/tests/src/lib.rs | 632 +++++++ libs/rust/tests/tests/keyblob_test.rs | 202 +++ libs/rust/wire/Cargo.toml | 30 + libs/rust/wire/fuzz/.gitignore | 3 + libs/rust/wire/fuzz/Cargo.toml | 34 + .../wire/fuzz/fuzz_targets/legacy_message.rs | 23 + libs/rust/wire/fuzz/fuzz_targets/message.rs | 25 + libs/rust/wire/src/keymint.rs | 1226 +++++++++++++ libs/rust/wire/src/legacy.rs | 817 +++++++++ libs/rust/wire/src/lib.rs | 581 ++++++ libs/rust/wire/src/rpc.rs | 80 + libs/rust/wire/src/secureclock.rs | 32 + libs/rust/wire/src/sharedsecret.rs | 27 + libs/rust/wire/src/tests.rs | 44 + libs/rust/wire/src/types.rs | 667 +++++++ 102 files changed, 26654 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE-2 create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 libs/rust/.gitignore create mode 100644 libs/rust/Cargo.toml create mode 100644 libs/rust/boringssl/Cargo.toml create mode 100644 libs/rust/boringssl/src/aes.rs create mode 100644 libs/rust/boringssl/src/aes_cmac.rs create mode 100644 libs/rust/boringssl/src/des.rs create mode 100644 libs/rust/boringssl/src/ec.rs create mode 100644 libs/rust/boringssl/src/eq.rs create mode 100644 libs/rust/boringssl/src/err.rs create mode 100644 libs/rust/boringssl/src/hmac.rs create mode 100644 libs/rust/boringssl/src/lib.rs create mode 100644 libs/rust/boringssl/src/rng.rs create mode 100644 libs/rust/boringssl/src/rsa.rs create mode 100644 libs/rust/boringssl/src/sha256.rs create mode 100644 libs/rust/boringssl/src/tests.rs create mode 100644 libs/rust/boringssl/src/types.rs create mode 100644 libs/rust/build.rs create mode 100644 libs/rust/common/Cargo.toml create mode 100644 libs/rust/common/fuzz/.gitignore create mode 100644 libs/rust/common/fuzz/Cargo.toml create mode 100644 libs/rust/common/fuzz/fuzz_targets/keyblob.rs create mode 100644 libs/rust/common/generated.cddl create mode 100644 libs/rust/common/src/bin/cddl-dump.rs create mode 100644 libs/rust/common/src/bin/keyblob-cddl-dump.rs create mode 100644 libs/rust/common/src/crypto.rs create mode 100644 libs/rust/common/src/crypto/aes.rs create mode 100644 libs/rust/common/src/crypto/des.rs create mode 100644 libs/rust/common/src/crypto/ec.rs create mode 100644 libs/rust/common/src/crypto/hmac.rs create mode 100644 libs/rust/common/src/crypto/rsa.rs create mode 100644 libs/rust/common/src/crypto/traits.rs create mode 100644 libs/rust/common/src/keyblob.rs create mode 100644 libs/rust/common/src/keyblob/keyblob.cddl create mode 100644 libs/rust/common/src/keyblob/legacy.rs create mode 100644 libs/rust/common/src/keyblob/legacy/tests.rs create mode 100644 libs/rust/common/src/keyblob/sdd_mem.rs create mode 100644 libs/rust/common/src/keyblob/tests.rs create mode 100644 libs/rust/common/src/lib.rs create mode 100644 libs/rust/common/src/tag.rs create mode 100644 libs/rust/common/src/tag/info.rs create mode 100644 libs/rust/common/src/tag/info/tests.rs create mode 100644 libs/rust/common/src/tag/legacy.rs create mode 100644 libs/rust/common/src/tag/tests.rs create mode 100644 libs/rust/derive/Cargo.toml create mode 100644 libs/rust/derive/src/lib.rs create mode 100644 libs/rust/derive/tests/integration_test.rs create mode 100644 libs/rust/hal/src/env.rs create mode 100644 libs/rust/hal/src/hal.rs create mode 100644 libs/rust/hal/src/hal/tests.rs create mode 100644 libs/rust/hal/src/keymint.rs create mode 100644 libs/rust/hal/src/lib.rs create mode 100644 libs/rust/hal/src/rpc.rs create mode 100644 libs/rust/hal/src/secureclock.rs create mode 100644 libs/rust/hal/src/sharedsecret.rs create mode 100644 libs/rust/hal/src/tests.rs create mode 100644 libs/rust/proto/storage.proto create mode 100755 libs/rust/scripts/cddl-gen create mode 100644 libs/rust/src/.gitignore create mode 100644 libs/rust/src/attest.rs create mode 100644 libs/rust/src/clock.rs create mode 100644 libs/rust/src/lib.rs create mode 100644 libs/rust/src/logging.rs create mode 100644 libs/rust/src/macros.rs create mode 100644 libs/rust/src/main.rs create mode 100644 libs/rust/src/rpc.rs create mode 100644 libs/rust/src/sdd.rs create mode 100644 libs/rust/src/soft.rs create mode 100644 libs/rust/ta/Cargo.toml create mode 100644 libs/rust/ta/fuzz/.gitignore create mode 100644 libs/rust/ta/fuzz/Cargo.toml create mode 100644 libs/rust/ta/fuzz/fuzz_targets/keydescription.rs create mode 100644 libs/rust/ta/src/cert.rs create mode 100644 libs/rust/ta/src/clock.rs create mode 100644 libs/rust/ta/src/device.rs create mode 100644 libs/rust/ta/src/keys.rs create mode 100644 libs/rust/ta/src/lib.rs create mode 100644 libs/rust/ta/src/operation.rs create mode 100644 libs/rust/ta/src/rkp.rs create mode 100644 libs/rust/ta/src/secret.rs create mode 100644 libs/rust/ta/src/tests.rs create mode 100644 libs/rust/tests/Cargo.toml create mode 100644 libs/rust/tests/src/bin/auth-keyblob-parse.rs create mode 100644 libs/rust/tests/src/bin/encrypted-keyblob-parse.rs create mode 100644 libs/rust/tests/src/lib.rs create mode 100644 libs/rust/tests/tests/keyblob_test.rs create mode 100644 libs/rust/wire/Cargo.toml create mode 100644 libs/rust/wire/fuzz/.gitignore create mode 100644 libs/rust/wire/fuzz/Cargo.toml create mode 100644 libs/rust/wire/fuzz/fuzz_targets/legacy_message.rs create mode 100644 libs/rust/wire/fuzz/fuzz_targets/message.rs create mode 100644 libs/rust/wire/src/keymint.rs create mode 100644 libs/rust/wire/src/legacy.rs create mode 100644 libs/rust/wire/src/lib.rs create mode 100644 libs/rust/wire/src/rpc.rs create mode 100644 libs/rust/wire/src/secureclock.rs create mode 100644 libs/rust/wire/src/sharedsecret.rs create mode 100644 libs/rust/wire/src/tests.rs create mode 100644 libs/rust/wire/src/types.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE-2 b/LICENSE-2 new file mode 100644 index 0000000..7be3d1b --- /dev/null +++ b/LICENSE-2 @@ -0,0 +1,16 @@ +Oh My Keymint License + +1. 您不得将本软件、本软件的任意部分或将本软件作为依赖的软件用于任何商业用途。该 + 商业用途包括但不限于以盈利为目的,将本软件、本软件的任意部分或将本软件作为依 + 赖的软件与其他资源、物品或服务捆绑销售。 + +2. 您不得暗示或明示本软件与其他软件有任何从属关系。 + +3. 未经本软件作者书面允许,您不得超出合理使用范围或协议许可范围使用本软件的名称。 + +4. 除非您所在的司法管辖区的适用法律另行规定,您同意将纠纷或争议提交至中国大陆境 + 内有管辖权的人民法院管辖。 + +5. 本协议与GNU Affero General Public License(以下简称AGPL)共同发挥效力, + 当本协议内容与AGPL冲突时,应当优先应用本协议内容,本协议仅覆盖本软件作者拥有 + 完全著作权的部分,对于使用其他协议的软件代码不发挥效力。 \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5469257 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,660 @@ +# GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +## Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public +License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for +the specific requirements. + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU AGPL, see . \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f89aa6 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Oh My Keymint + +Custom keymint implementation for Android Keystore Spoofer + +> [!WARNING] +> This is a toy project. No ANY guarantees are made regarding performance or stability. + +# License + +**YOU MUST AGREE TO BOTH OF THE LICENSE BEFORE USING THIS SOFTWARE.** + +`AGPL-3.0-or-later` + +``` +OhMyKeymint - Custom keymint implementation for Android Keystore Spoofer +Copyright (C) 2025 James Clef + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +``` + +`Oh My Keymint License` + +``` +1. 您不得将本软件、本软件的任意部分或将本软件作为依赖的软件用于任何商业用途。该 + 商业用途包括但不限于以盈利为目的,将本软件、本软件的任意部分或将本软件作为依 + 赖的软件与其他资源、物品或服务捆绑销售。 + +2. 您不得暗示或明示本软件与其他软件有任何从属关系。 + +3. 未经本软件作者书面允许,您不得超出合理使用范围或协议许可范围使用本软件的名称。 + +4. 除非您所在的司法管辖区的适用法律另行规定,您同意将纠纷或争议提交至中国大陆境 + 内有管辖权的人民法院管辖。 + +5. 本协议与GNU Affero General Public License(以下简称AGPL)共同发挥效力, + 当本协议内容与AGPL冲突时,应当优先应用本协议内容,本协议仅覆盖本软件作者拥有 + 完全著作权的部分,对于使用其他协议的软件代码不发挥效力。 + +``` + +# Credit + +Some code from [AOSP](https://source.android.com/) + +License: `Apache-2.0` +``` +Copyright 2022, 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. +``` \ No newline at end of file diff --git a/libs/rust/.gitignore b/libs/rust/.gitignore new file mode 100644 index 0000000..ee300e7 --- /dev/null +++ b/libs/rust/.gitignore @@ -0,0 +1,20 @@ +target.apk +target +.vscode + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +/omk diff --git a/libs/rust/Cargo.toml b/libs/rust/Cargo.toml new file mode 100644 index 0000000..23cc583 --- /dev/null +++ b/libs/rust/Cargo.toml @@ -0,0 +1,66 @@ +cargo-features = ["profile-rustflags", "trim-paths"] + +[package] +name = "OhMyKeymint" +version = "0.1.1" +edition = "2021" + +[[bin]] +name = "keymint" +path = "src/main.rs" + +[lib] +name = "ohmykeymint" +path = "src/lib.rs" +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +jni = "0.21.1" +syscalls = "0.6.15" +kmr-derive = { path = "derive" } +kmr-common = { path = "common" } +kmr-crypto-boring = { path = "boringssl" } +kmr-ta = { path = "ta" } +kmr-wire = { path = "wire" } +libc = "0.2.176" +prost = "0.14.1" +prost-types = "0.14.1" +hex = "0.4.3" +log4rs = "1.4.0" +log = "0.4.28" + +[target.'cfg(target_os = "android")'.dependencies] +android_logger_lite = "0.1.0" + + +[profile.release] +opt-level = 'z' # Optimize for size +lto = true # Enable link-time optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations +panic = 'abort' # Abort on panic +strip = true # Strip symbols from binary* +debuginfo = 0 # Disable debug info +trim-paths = "all" + +[workspace] +members = [ + "boringssl", + "common", + "derive", + "ta", + "tests", + "wire", +] + +[patch.crates-io] +kmr-derive = { path = "derive" } +kmr-common = { path = "common" } +kmr-crypto-boring = { path = "boringssl" } +kmr-ta = { path = "ta" } +kmr-tests = { path = "tests" } +kmr-wire = { path = "wire" } + +[build-dependencies] +prost-build = "0.14.1" diff --git a/libs/rust/boringssl/Cargo.toml b/libs/rust/boringssl/Cargo.toml new file mode 100644 index 0000000..2598dfd --- /dev/null +++ b/libs/rust/boringssl/Cargo.toml @@ -0,0 +1,24 @@ +# Note that Cargo is not an officially supported build tool (Android's Soong is the official +# tool). This Cargo.toml file is included purely for the convenience of KeyMint developers. + +[package] +name = "kmr-crypto-boring" +authors = ["David Drysdale "] +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[dependencies] +ffi = { package = "openssl-sys", version = "^0.9.75" } +foreign-types = "0.3.1" +kmr-common = "*" +kmr-wire = "*" +libc = "^0.2.112" +log = "^0.4" +openssl = "^0.10.36" + +[dev-dependencies] +kmr-tests = "*" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(soong)'] } \ No newline at end of file diff --git a/libs/rust/boringssl/src/aes.rs b/libs/rust/boringssl/src/aes.rs new file mode 100644 index 0000000..f591eb6 --- /dev/null +++ b/libs/rust/boringssl/src/aes.rs @@ -0,0 +1,286 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of AES. +use crate::{openssl_err, openssl_err_or, ossl}; +use Box; +use Vec; +use core::cmp::min; +use kmr_common::{ + crypto, crypto::OpaqueOr, explicit, km_err, vec_try, vec_try_with_capacity, Error, + FallibleAllocExt, +}; +use openssl::symm::{Cipher, Crypter}; + +/// [`crypto::Aes`] implementation based on BoringSSL. +pub struct BoringAes; + +impl crypto::Aes for BoringAes { + fn begin( + &self, + key: OpaqueOr, + mode: crypto::aes::CipherMode, + dir: crypto::SymmetricOperation, + ) -> Result, Error> { + let key = explicit!(key)?; + let dir_mode = match dir { + crypto::SymmetricOperation::Encrypt => openssl::symm::Mode::Encrypt, + crypto::SymmetricOperation::Decrypt => openssl::symm::Mode::Decrypt, + }; + let crypter = match mode { + crypto::aes::CipherMode::EcbNoPadding | crypto::aes::CipherMode::EcbPkcs7Padding => { + let (cipher, key) = match &key { + crypto::aes::Key::Aes128(k) => (Cipher::aes_128_ecb(), &k[..]), + crypto::aes::Key::Aes192(k) => (Cipher::aes_192_ecb(), &k[..]), + crypto::aes::Key::Aes256(k) => (Cipher::aes_256_ecb(), &k[..]), + }; + let mut crypter = Crypter::new(cipher, dir_mode, key, None) + .map_err(openssl_err!("failed to create ECB Crypter"))?; + if let crypto::aes::CipherMode::EcbPkcs7Padding = mode { + crypter.pad(true); + } else { + crypter.pad(false); + } + crypter + } + + crypto::aes::CipherMode::CbcNoPadding { nonce: n } + | crypto::aes::CipherMode::CbcPkcs7Padding { nonce: n } => { + let (cipher, key) = match &key { + crypto::aes::Key::Aes128(k) => (Cipher::aes_128_cbc(), &k[..]), + crypto::aes::Key::Aes192(k) => (Cipher::aes_192_cbc(), &k[..]), + crypto::aes::Key::Aes256(k) => (Cipher::aes_256_cbc(), &k[..]), + }; + let mut crypter = Crypter::new(cipher, dir_mode, key, Some(&n[..])) + .map_err(openssl_err!("failed to create CBC Crypter"))?; + if let crypto::aes::CipherMode::CbcPkcs7Padding { nonce: _ } = mode { + crypter.pad(true); + } else { + crypter.pad(false); + } + crypter + } + + crypto::aes::CipherMode::Ctr { nonce: n } => { + let (cipher, key) = match &key { + crypto::aes::Key::Aes128(k) => (Cipher::aes_128_ctr(), &k[..]), + crypto::aes::Key::Aes192(k) => (Cipher::aes_192_ctr(), &k[..]), + crypto::aes::Key::Aes256(k) => (Cipher::aes_256_ctr(), &k[..]), + }; + Crypter::new(cipher, dir_mode, key, Some(&n[..])) + .map_err(openssl_err!("failed to create CTR Crypter"))? + } + }; + + Ok(Box::new(BoringAesOperation { crypter })) + } + + fn begin_aead( + &self, + key: OpaqueOr, + mode: crypto::aes::GcmMode, + dir: crypto::SymmetricOperation, + ) -> Result, Error> { + let key = explicit!(key)?; + let dir_mode = match dir { + crypto::SymmetricOperation::Encrypt => openssl::symm::Mode::Encrypt, + crypto::SymmetricOperation::Decrypt => openssl::symm::Mode::Decrypt, + }; + let crypter = match mode { + crypto::aes::GcmMode::GcmTag12 { nonce: n } + | crypto::aes::GcmMode::GcmTag13 { nonce: n } + | crypto::aes::GcmMode::GcmTag14 { nonce: n } + | crypto::aes::GcmMode::GcmTag15 { nonce: n } + | crypto::aes::GcmMode::GcmTag16 { nonce: n } => { + let (cipher, key) = match &key { + crypto::aes::Key::Aes128(k) => (Cipher::aes_128_gcm(), &k[..]), + crypto::aes::Key::Aes192(k) => (Cipher::aes_192_gcm(), &k[..]), + crypto::aes::Key::Aes256(k) => (Cipher::aes_256_gcm(), &k[..]), + }; + Crypter::new(cipher, dir_mode, key, Some(&n[..])).map_err(openssl_err!( + "failed to create GCM Crypter for {:?} {:?}", + mode, + dir + ))? + } + }; + + Ok(match dir { + crypto::SymmetricOperation::Encrypt => Box::new({ + BoringAesGcmEncryptOperation { mode, inner: BoringAesOperation { crypter } } + }), + crypto::SymmetricOperation::Decrypt => Box::new(BoringAesGcmDecryptOperation { + crypter, + decrypt_tag_len: mode.tag_len(), + pending_input_tail: vec_try_with_capacity!(mode.tag_len())?, + }), + }) + } +} + +/// AES operation based on BoringSSL. +pub struct BoringAesOperation { + crypter: openssl::symm::Crypter, +} + +impl crypto::EmittingOperation for BoringAesOperation { + fn update(&mut self, data: &[u8]) -> Result, Error> { + let mut output = vec_try![0; data.len() + crypto::aes::BLOCK_SIZE]?; + let out_len = self + .crypter + .update(data, &mut output) + .map_err(openssl_err!("update {} bytes from input failed", data.len()))?; + output.truncate(out_len); + Ok(output) + } + + fn finish(mut self: Box) -> Result, Error> { + let mut output = vec_try![0; crypto::aes::BLOCK_SIZE]?; + let out_len = ossl!(self.crypter.finalize(&mut output))?; + output.truncate(out_len); + Ok(output) + } +} + +/// AES-GCM encrypt operation based on BoringSSL. +pub struct BoringAesGcmEncryptOperation { + mode: crypto::aes::GcmMode, + inner: BoringAesOperation, +} + +impl crypto::AadOperation for BoringAesGcmEncryptOperation { + fn update_aad(&mut self, aad: &[u8]) -> Result<(), Error> { + ossl!(self.inner.crypter.aad_update(aad)) + } +} + +impl crypto::EmittingOperation for BoringAesGcmEncryptOperation { + fn update(&mut self, data: &[u8]) -> Result, Error> { + self.inner.update(data) + } + + fn finish(mut self: Box) -> Result, Error> { + let mut output = vec_try![0; crypto::aes::BLOCK_SIZE + self.mode.tag_len()]?; + let offset = self + .inner + .crypter + .finalize(&mut output) + .map_err(openssl_err_or!(VerificationFailed, "failed to finalize"))?; + + self.inner + .crypter + .get_tag(&mut output[offset..offset + self.mode.tag_len()]) + .map_err(openssl_err!("failed to get tag of len {}", self.mode.tag_len()))?; + output.truncate(offset + self.mode.tag_len()); + Ok(output) + } +} + +/// AES-GCM decrypt operation based on BoringSSL. +pub struct BoringAesGcmDecryptOperation { + crypter: openssl::symm::Crypter, + + // Size of a final tag when decrypting. + decrypt_tag_len: usize, + + // For decryption, the last `decrypt_tag_len` bytes of input must be fed in separately. + // However, the overall size of the input data is not known in advance, so we need to hold up to + // `decrypt_tag_len` bytes on input in reserve until `finish()`. + pending_input_tail: Vec, // Capacity = decrypt_tag_len +} + +impl crypto::AadOperation for BoringAesGcmDecryptOperation { + fn update_aad(&mut self, aad: &[u8]) -> Result<(), Error> { + ossl!(self.crypter.aad_update(aad)) + } +} + +impl crypto::EmittingOperation for BoringAesGcmDecryptOperation { + fn update(&mut self, data: &[u8]) -> Result, Error> { + // The current input is the (self.pending_input_tail || data) combination. + let combined_len = self.pending_input_tail.len() + data.len(); + if combined_len <= self.decrypt_tag_len { + // Adding on this data is still not enough for more than just a tag, + // so save off the input data for next time and return. + self.pending_input_tail.try_extend_from_slice(data)?; + return Ok(Vec::new()); + } + + // At this point the combination (self.pending_input_tail || data) includes enough data to both: + // - feed some into the cipher + // - still keep a full self.decrypt_tag_len worth of data still pending. + let cipherable_len = combined_len - self.decrypt_tag_len; + let cipherable_from_pending = min(cipherable_len, self.pending_input_tail.len()); + let cipherable_from_data = cipherable_len - cipherable_from_pending; + + let mut output = vec_try![0; data.len()]?; + let mut offset = 0; + if cipherable_from_pending > 0 { + offset = self + .crypter + .update(&self.pending_input_tail[..cipherable_from_pending], &mut output) + .map_err(openssl_err!( + "update {} bytes from pending failed", + cipherable_from_pending + ))?; + } + if cipherable_from_data > 0 { + let out_len = self + .crypter + .update(&data[..cipherable_from_data], &mut output[offset..]) + .map_err(openssl_err!("update {} bytes from input failed", cipherable_from_data))?; + offset += out_len; + } + output.truncate(offset); + + // Reset `self.pending_input_tail` to the unused data. + let leftover_pending = self.pending_input_tail.len() - cipherable_from_pending; + self.pending_input_tail.resize(self.decrypt_tag_len, 0); + self.pending_input_tail.copy_within(cipherable_from_pending.., 0); + self.pending_input_tail[leftover_pending..].copy_from_slice(&data[cipherable_from_data..]); + + Ok(output) + } + + fn finish(mut self: Box) -> Result, Error> { + // Need to feed in the entire tag before completion. + if self.pending_input_tail.len() != self.decrypt_tag_len { + return Err(km_err!( + InvalidTag, + "only {} bytes of pending data, need {}", + self.pending_input_tail.len(), + self.decrypt_tag_len + )); + } + self.crypter.set_tag(&self.pending_input_tail).map_err(openssl_err!( + "failed to set {} bytes of tag", + self.pending_input_tail.len() + ))?; + + // Feeding in just the tag should not result in any output data. + let mut output = Vec::new(); + let out_len = self + .crypter + .finalize(&mut output) + .map_err(openssl_err_or!(VerificationFailed, "failed to finalize"))?; + if out_len != 0 { + return Err(km_err!( + BoringSslError, + "finalizing AES-GCM tag produced {} bytes of data!", + out_len + )); + } + Ok(output) + } +} diff --git a/libs/rust/boringssl/src/aes_cmac.rs b/libs/rust/boringssl/src/aes_cmac.rs new file mode 100644 index 0000000..37ca00c --- /dev/null +++ b/libs/rust/boringssl/src/aes_cmac.rs @@ -0,0 +1,117 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of AES-CMAC. +use crate::types::CmacCtx; +use crate::{malloc_err, openssl_last_err}; +use Box; +use Vec; +#[cfg(target_os = "android")] use bssl_sys as ffi; +use ffi; +use kmr_common::{crypto, crypto::OpaqueOr, explicit, km_err, vec_try, Error}; +use log::error; + +/// [`crypto::AesCmac`] implementation based on BoringSSL. +pub struct BoringAesCmac; + +impl crypto::AesCmac for BoringAesCmac { + fn begin( + &self, + key: OpaqueOr, + ) -> Result, Error> { + let key = explicit!(key)?; + // Safety: all of the `ffi::EVP_aes__cbc` functions return a non-null valid pointer. + let (cipher, k) = unsafe { + match &key { + crypto::aes::Key::Aes128(k) => (ffi::EVP_aes_128_cbc(), &k[..]), + crypto::aes::Key::Aes192(k) => (ffi::EVP_aes_192_cbc(), &k[..]), + crypto::aes::Key::Aes256(k) => (ffi::EVP_aes_256_cbc(), &k[..]), + } + }; + + let op = BoringAesCmacOperation { + // Safety: raw pointer is immediately checked for null below, and BoringSSL only emits + // valid pointers or null. + ctx: unsafe { CmacCtx(ffi::CMAC_CTX_new()) }, + }; + if op.ctx.0.is_null() { + return Err(malloc_err!()); + } + + // Safety: `op.ctx` is known non-null and valid, as is `cipher`. `key_len` is length of + // `key.0`, which is a valid `Vec`. + let result = unsafe { + ffi::CMAC_Init( + op.ctx.0, + k.as_ptr() as *const libc::c_void, + k.len(), + cipher, + core::ptr::null_mut(), + ) + }; + if result != 1 { + error!("Failed to CMAC_Init()"); + return Err(openssl_last_err()); + } + Ok(Box::new(op)) + } +} + +/// AES-CMAC implementation based on BoringSSL. +/// +/// This implementation uses the `unsafe` wrappers around `CMAC_*` functions directly, because +/// BoringSSL does not support the `EVP_PKEY_CMAC` implementations that are used in the rust-openssl +/// crate. +pub struct BoringAesCmacOperation { + // Safety: `ctx` is always non-null and valid except for initial error path in `begin()` + ctx: CmacCtx, +} + +impl core::ops::Drop for BoringAesCmacOperation { + fn drop(&mut self) { + // Safety: `self.ctx` might be null (in the error path when `ffi::CMAC_CTX_new` fails) + // but `ffi::CMAC_CTX_free` copes with null. + unsafe { + ffi::CMAC_CTX_free(self.ctx.0); + } + } +} + +impl crypto::AccumulatingOperation for BoringAesCmacOperation { + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + // Safety: `self.ctx` is non-null and valid, and `data` is a valid slice. + let result = unsafe { ffi::CMAC_Update(self.ctx.0, data.as_ptr() as *const libc::c_void, data.len()) }; + if result != 1 { + return Err(openssl_last_err()); + } + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + let mut output_len: usize = crypto::aes::BLOCK_SIZE; + let mut output = vec_try![0; crypto::aes::BLOCK_SIZE]?; + // Safety: `self.ctx` is non-null and valid; `output_len` is correct size of `output` + // buffer. + let result = unsafe { + ffi::CMAC_Final(self.ctx.0, output.as_mut_ptr(), &mut output_len as *mut usize) + }; + if result != 1 { + return Err(openssl_last_err()); + } + if output_len != crypto::aes::BLOCK_SIZE { + return Err(km_err!(BoringSslError, "Unexpected CMAC output size of {}", output_len)); + } + Ok(output) + } +} diff --git a/libs/rust/boringssl/src/des.rs b/libs/rust/boringssl/src/des.rs new file mode 100644 index 0000000..1b0f6de --- /dev/null +++ b/libs/rust/boringssl/src/des.rs @@ -0,0 +1,90 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of 3-DES. +use crate::{openssl_err, ossl}; +use Box; +use Vec; +use kmr_common::{crypto, crypto::OpaqueOr, explicit, vec_try, Error}; +use openssl::symm::{Cipher, Crypter}; + +/// [`crypto::Des`] implementation based on BoringSSL. +pub struct BoringDes; + +impl crypto::Des for BoringDes { + fn begin( + &self, + key: OpaqueOr, + mode: crypto::des::Mode, + dir: crypto::SymmetricOperation, + ) -> Result, Error> { + let key = explicit!(key)?; + let dir_mode = match dir { + crypto::SymmetricOperation::Encrypt => openssl::symm::Mode::Encrypt, + crypto::SymmetricOperation::Decrypt => openssl::symm::Mode::Decrypt, + }; + let crypter = match mode { + crypto::des::Mode::EcbNoPadding | crypto::des::Mode::EcbPkcs7Padding => { + let cipher = Cipher::des_ede3(); + let mut crypter = Crypter::new(cipher, dir_mode, &key.0, None) + .map_err(openssl_err!("failed to create ECB Crypter"))?; + if let crypto::des::Mode::EcbPkcs7Padding = mode { + crypter.pad(true); + } else { + crypter.pad(false); + } + crypter + } + + crypto::des::Mode::CbcNoPadding { nonce: n } + | crypto::des::Mode::CbcPkcs7Padding { nonce: n } => { + let cipher = Cipher::des_ede3_cbc(); + let mut crypter = Crypter::new(cipher, dir_mode, &key.0, Some(&n[..])) + .map_err(openssl_err!("failed to create CBC Crypter"))?; + if let crypto::des::Mode::CbcPkcs7Padding { nonce: _ } = mode { + crypter.pad(true); + } else { + crypter.pad(false); + } + crypter + } + }; + + Ok(Box::new(BoringDesOperation { crypter })) + } +} + +/// DES operation based on BoringSSL. +pub struct BoringDesOperation { + crypter: openssl::symm::Crypter, +} + +impl crypto::EmittingOperation for BoringDesOperation { + fn update(&mut self, data: &[u8]) -> Result, Error> { + let mut output = vec_try![0; data.len() + crypto::des::BLOCK_SIZE]?; + let out_len = self + .crypter + .update(data, &mut output[..]) + .map_err(openssl_err!("update with {} bytes of data failed", data.len()))?; + output.truncate(out_len); + Ok(output) + } + + fn finish(mut self: Box) -> Result, Error> { + let mut output = vec_try![0; crypto::des::BLOCK_SIZE]?; + let out_len = ossl!(self.crypter.finalize(&mut output))?; + output.truncate(out_len); + Ok(output) + } +} diff --git a/libs/rust/boringssl/src/ec.rs b/libs/rust/boringssl/src/ec.rs new file mode 100644 index 0000000..f19c534 --- /dev/null +++ b/libs/rust/boringssl/src/ec.rs @@ -0,0 +1,449 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of elliptic curve functionality. +use crate::types::{EvpMdCtx, EvpPkeyCtx}; +use crate::{cvt, cvt_p, digest_into_openssl, openssl_err, openssl_last_err, ossl}; +use Box; +use Vec; +#[cfg(soong)] +use bssl_sys as ffi; +use core::ops::DerefMut; +use core::ptr; +use foreign_types::ForeignType; +use kmr_common::{ + crypto, + crypto::{ec, ec::Key, AccumulatingOperation, CurveType, OpaqueOr}, + explicit, km_err, vec_try, Error, FallibleAllocExt, +}; +use kmr_wire::{ + keymint, + keymint::{Digest, EcCurve}, +}; +use openssl::hash::MessageDigest; + +#[cfg(soong)] +fn private_key_from_der_for_group( + der: &[u8], + group: &openssl::ec::EcGroupRef, +) -> Result, openssl::error::ErrorStack> { + // This method is an Android modification to the rust-openssl crate. + openssl::ec::EcKey::private_key_from_der_for_group(der, group) +} + +#[cfg(not(soong))] +fn private_key_from_der_for_group( + der: &[u8], + _group: &openssl::ec::EcGroupRef, +) -> Result, openssl::error::ErrorStack> { + // This doesn't work if the encoded data is missing the curve. + openssl::ec::EcKey::private_key_from_der(der) +} + +/// [`crypto::Ec`] implementation based on BoringSSL. +pub struct BoringEc { + /// Zero-sized private field to force use of [`default()`] for initialization. + _priv: core::marker::PhantomData<()>, +} + +impl core::default::Default for BoringEc { + fn default() -> Self { + ffi::init(); + Self { _priv: core::marker::PhantomData } + } +} + +impl crypto::Ec for BoringEc { + fn generate_nist_key( + &self, + _rng: &mut dyn crypto::Rng, + curve: ec::NistCurve, + _params: &[keymint::KeyParam], + ) -> Result { + let ec_key = ossl!(openssl::ec::EcKey::::generate( + nist_curve_to_group(curve)?.as_ref() + ))?; + let nist_key = ec::NistKey(ossl!(ec_key.private_key_to_der())?); + let key = match curve { + ec::NistCurve::P224 => Key::P224(nist_key), + ec::NistCurve::P256 => Key::P256(nist_key), + ec::NistCurve::P384 => Key::P384(nist_key), + ec::NistCurve::P521 => Key::P521(nist_key), + }; + Ok(crypto::KeyMaterial::Ec(curve.into(), CurveType::Nist, key.into())) + } + + fn generate_ed25519_key( + &self, + _rng: &mut dyn crypto::Rng, + _params: &[keymint::KeyParam], + ) -> Result { + let pkey = ossl!(openssl::pkey::PKey::generate_ed25519())?; + let key = ossl!(pkey.raw_private_key())?; + let key: [u8; ec::CURVE25519_PRIV_KEY_LEN] = key.try_into().map_err(|e| { + km_err!(UnsupportedKeySize, "generated Ed25519 key of unexpected size: {:?}", e) + })?; + let key = Key::Ed25519(ec::Ed25519Key(key)); + Ok(crypto::KeyMaterial::Ec(EcCurve::Curve25519, CurveType::EdDsa, key.into())) + } + + fn generate_x25519_key( + &self, + _rng: &mut dyn crypto::Rng, + _params: &[keymint::KeyParam], + ) -> Result { + let pkey = ossl!(openssl::pkey::PKey::generate_x25519())?; + let key = ossl!(pkey.raw_private_key())?; + let key: [u8; ec::CURVE25519_PRIV_KEY_LEN] = key.try_into().map_err(|e| { + km_err!(UnsupportedKeySize, "generated X25519 key of unexpected size: {:?}", e) + })?; + let key = Key::X25519(ec::X25519Key(key)); + Ok(crypto::KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, key.into())) + } + + fn nist_public_key(&self, key: &ec::NistKey, curve: ec::NistCurve) -> Result, Error> { + let group = nist_curve_to_group(curve)?; + let ec_key = ossl!(private_key_from_der_for_group(&key.0, group.as_ref()))?; + let pt = ec_key.public_key(); + let mut bn_ctx = ossl!(openssl::bn::BigNumContext::new())?; + ossl!(pt.to_bytes( + group.as_ref(), + openssl::ec::PointConversionForm::UNCOMPRESSED, + bn_ctx.deref_mut() + )) + } + + fn ed25519_public_key(&self, key: &ec::Ed25519Key) -> Result, Error> { + let pkey = ossl!(openssl::pkey::PKey::private_key_from_raw_bytes( + &key.0, + openssl::pkey::Id::ED25519 + ))?; + ossl!(pkey.raw_public_key()) + } + + fn x25519_public_key(&self, key: &ec::X25519Key) -> Result, Error> { + let pkey = ossl!(openssl::pkey::PKey::private_key_from_raw_bytes( + &key.0, + openssl::pkey::Id::X25519 + ))?; + ossl!(pkey.raw_public_key()) + } + + fn begin_agree(&self, key: OpaqueOr) -> Result, Error> { + let key = explicit!(key)?; + // Maximum size for a `SubjectPublicKeyInfo` that holds an EC public key is: + // + // 30 LL SEQUENCE + len (SubjectPublicKeyInfo) + // 30 LL SEQUENCE + len (AlgorithmIdentifier) + // 06 07 OID + len + // 2a8648ce3d0201 (ecPublicKey OID) + // 06 08 OID + len + // 2a8648ce3d030107 (P-256 curve OID, which is the longest) + // 03 42 BIT STRING + len + // 00 zero pad bits + // 04 uncompressed + // ... 66 bytes of P-521 X coordinate + // ... 66 bytes of P-521 Y coordinate + // + // Round up a bit just in case. + let max_size = 164; + Ok(Box::new(BoringEcAgreeOperation { key, pending_input: Vec::new(), max_size })) + } + + fn begin_sign( + &self, + key: OpaqueOr, + digest: Digest, + ) -> Result, Error> { + let key = explicit!(key)?; + let curve = key.curve(); + match key { + Key::P224(key) | Key::P256(key) | Key::P384(key) | Key::P521(key) => { + let curve = ec::NistCurve::try_from(curve)?; + if let Some(digest) = digest_into_openssl(digest) { + Ok(Box::new(BoringEcDigestSignOperation::new(key, curve, digest)?)) + } else { + Ok(Box::new(BoringEcUndigestSignOperation::new(key, curve)?)) + } + } + Key::Ed25519(key) => Ok(Box::new(BoringEd25519SignOperation::new(key)?)), + Key::X25519(_) => { + Err(km_err!(IncompatibleAlgorithm, "X25519 key not valid for signing")) + } + } + } +} + +/// ECDH operation based on BoringSSL. +pub struct BoringEcAgreeOperation { + key: Key, + pending_input: Vec, // Limited to `max_size` below. + // Size of a `SubjectPublicKeyInfo` holding peer public key. + max_size: usize, +} + +impl crypto::AccumulatingOperation for BoringEcAgreeOperation { + fn max_input_size(&self) -> Option { + Some(self.max_size) + } + + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + self.pending_input.try_extend_from_slice(data)?; + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + let peer_key = ossl!(openssl::pkey::PKey::public_key_from_der(&self.pending_input))?; + match &self.key { + Key::P224(key) | Key::P256(key) | Key::P384(key) | Key::P521(key) => { + let group = nist_key_to_group(&self.key)?; + let ec_key = ossl!(private_key_from_der_for_group(&key.0, group.as_ref()))?; + let pkey = ossl!(openssl::pkey::PKey::from_ec_key(ec_key))?; + let mut deriver = ossl!(openssl::derive::Deriver::new(&pkey))?; + ossl!(deriver.set_peer(&peer_key)) + .map_err(|e| km_err!(InvalidArgument, "peer key invalid: {:?}", e))?; + let derived = ossl!(deriver.derive_to_vec())?; + Ok(derived) + } + #[cfg(soong)] + Key::X25519(key) => { + // The BoringSSL `EVP_PKEY` interface does not support X25519, so need to invoke the + // `ffi:X25519()` method directly. First need to extract the raw peer key from the + // `SubjectPublicKeyInfo` it arrives in. + let peer_key = + ossl!(openssl::pkey::PKey::public_key_from_der(&self.pending_input))?; + let peer_key_type = peer_key.id(); + if peer_key_type != openssl::pkey::Id::X25519 { + return Err(km_err!( + InvalidArgument, + "peer key for {:?} not supported with X25519", + peer_key_type + )); + } + let peer_key_data = ossl!(peer_key.raw_public_key())?; + if peer_key_data.len() != ffi::X25519_PUBLIC_VALUE_LEN as usize { + return Err(km_err!( + UnsupportedKeySize, + "peer raw key invalid length {}", + peer_key_data.len() + )); + } + + let mut sig = vec_try![0; ffi::X25519_SHARED_KEY_LEN as usize]?; + // Safety: all pointer arguments need to point to 32-byte memory areas, enforced + // above and in the definition of [`ec::X25519Key`]. + let result = unsafe { + ffi::X25519(sig.as_mut_ptr(), &key.0 as *const u8, peer_key_data.as_ptr()) + }; + if result == 1 { + Ok(sig) + } else { + Err(super::openssl_last_err()) + } + } + #[cfg(not(soong))] + Key::X25519(_) => Err(km_err!(UnsupportedEcCurve, "X25519 not supported in cargo")), + Key::Ed25519(_) => { + Err(km_err!(IncompatibleAlgorithm, "Ed25519 key not valid for agreement")) + } + } + } +} + +/// ECDSA signing operation based on BoringSSL, when an external digest is used. +pub struct BoringEcDigestSignOperation { + // Safety: `pkey` internally holds a pointer to BoringSSL-allocated data (`EVP_PKEY`), + // as do both of the raw pointers. This means that this item stays valid under moves, + // because the FFI-allocated data doesn't move. + pkey: openssl::pkey::PKey, + + // Safety invariant: both `pctx` and `md_ctx` are non-`nullptr` and valid once item is + // constructed. + md_ctx: EvpMdCtx, + pctx: EvpPkeyCtx, +} + +impl Drop for BoringEcDigestSignOperation { + fn drop(&mut self) { + // Safety: `EVP_MD_CTX_free()` handles `nullptr`, so it's fine to drop a partly-constructed + // item. `pctx` is owned by the `md_ctx`, so no need to explicitly free it. + unsafe { + ffi::EVP_MD_CTX_free(self.md_ctx.0); + } + } +} + +impl BoringEcDigestSignOperation { + fn new(key: ec::NistKey, curve: ec::NistCurve, digest: MessageDigest) -> Result { + let group = nist_curve_to_group(curve)?; + let ec_key = ossl!(private_key_from_der_for_group(&key.0, group.as_ref()))?; + let pkey = ossl!(openssl::pkey::PKey::from_ec_key(ec_key))?; + + // Safety: each of the raw pointers have to be non-nullptr (and thus valid, as BoringSSL + // emits only valid pointers or nullptr) to get to the point where they're used. + unsafe { + let mut op = BoringEcDigestSignOperation { + pkey, + md_ctx: EvpMdCtx(cvt_p(ffi::EVP_MD_CTX_new())?), + pctx: EvpPkeyCtx(ptr::null_mut()), + }; + + let r = ffi::EVP_DigestSignInit( + op.md_ctx.0, + &mut op.pctx.0, + digest.as_ptr(), + ptr::null_mut(), + op.pkey.as_ptr(), + ); + if r != 1 { + return Err(openssl_last_err()); + } + if op.pctx.0.is_null() { + return Err(km_err!(BoringSslError, "no PCTX!")); + } + // Safety invariant: both `pctx` and `md_ctx` are non-`nullptr` and valid on success. + Ok(op) + } + } +} + +impl crypto::AccumulatingOperation for BoringEcDigestSignOperation { + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + // Safety: `data` is a valid slice, and `self.md_ctx` is non-`nullptr` and valid. + unsafe { + cvt(ffi::EVP_DigestUpdate(self.md_ctx.0, data.as_ptr() as *const _, data.len()))?; + } + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + let mut max_siglen = 0; + // Safety: `self.md_ctx` is non-`nullptr` and valid. + unsafe { + cvt(ffi::EVP_DigestSignFinal(self.md_ctx.0, ptr::null_mut(), &mut max_siglen))?; + } + let mut buf = vec_try![0; max_siglen]?; + let mut actual_siglen = max_siglen; + // Safety: `self.md_ctx` is non-`nullptr` and valid, and `buf` does have `actual_siglen` + // bytes. + unsafe { + cvt(ffi::EVP_DigestSignFinal( + self.md_ctx.0, + buf.as_mut_ptr() as *mut _, + &mut actual_siglen, + ))?; + } + buf.truncate(actual_siglen); + Ok(buf) + } +} + +/// ECDSA signing operation based on BoringSSL, when data is undigested. +pub struct BoringEcUndigestSignOperation { + ec_key: openssl::ec::EcKey, + pending_input: Vec, + max_size: usize, +} + +impl BoringEcUndigestSignOperation { + fn new(key: ec::NistKey, curve: ec::NistCurve) -> Result { + let group = nist_curve_to_group(curve)?; + let ec_key = ossl!(private_key_from_der_for_group(&key.0, group.as_ref()))?; + // Input to an undigested EC signing operation must be smaller than key size. + Ok(Self { ec_key, pending_input: Vec::new(), max_size: curve.coord_len() }) + } +} + +impl crypto::AccumulatingOperation for BoringEcUndigestSignOperation { + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + // For ECDSA signing, extra data beyond the maximum size is ignored (rather than being + // rejected via the `max_input_size()` trait method). + let max_extra_data = self.max_size - self.pending_input.len(); + if max_extra_data > 0 { + let len = core::cmp::min(max_extra_data, data.len()); + self.pending_input.try_extend_from_slice(&data[..len])?; + } + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + // BoringSSL doesn't support `EVP_PKEY` use without digest, so use low-level ECDSA + // functionality. + let sig = ossl!(openssl::ecdsa::EcdsaSig::sign(&self.pending_input, &self.ec_key))?; + let sig = ossl!(sig.to_der())?; + Ok(sig) + } +} + +/// EdDSA signing operation based on BoringSSL for Ed25519. +pub struct BoringEd25519SignOperation { + pkey: openssl::pkey::PKey, + pending_input: Vec, +} + +impl BoringEd25519SignOperation { + fn new(key: ec::Ed25519Key) -> Result { + let pkey = ossl!(openssl::pkey::PKey::private_key_from_raw_bytes( + &key.0, + openssl::pkey::Id::ED25519 + ))?; + Ok(Self { pkey, pending_input: Vec::new() }) + } +} + +impl crypto::AccumulatingOperation for BoringEd25519SignOperation { + fn max_input_size(&self) -> Option { + // Ed25519 has an internal digest so could theoretically take arbitrary amounts of + // data. However, BoringSSL does not support incremental data feeding for Ed25519 so + // instead impose a message size limit (as required by the KeyMint HAL spec). + Some(ec::MAX_ED25519_MSG_SIZE) + } + + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + // OK to accumulate data as there is a size limit. + self.pending_input.try_extend_from_slice(data)?; + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + let mut signer = ossl!(openssl::sign::Signer::new_without_digest(&self.pkey))?; + let sig = ossl!(signer.sign_oneshot_to_vec(&self.pending_input))?; + Ok(sig) + } +} + +fn nist_curve_to_group(curve: ec::NistCurve) -> Result { + use openssl::nid::Nid; + openssl::ec::EcGroup::from_curve_name(match curve { + ec::NistCurve::P224 => Nid::SECP224R1, + ec::NistCurve::P256 => Nid::X9_62_PRIME256V1, + ec::NistCurve::P384 => Nid::SECP384R1, + ec::NistCurve::P521 => Nid::SECP521R1, + }) + .map_err(openssl_err!("failed to determine EcGroup")) +} + +fn nist_key_to_group(key: &ec::Key) -> Result { + use openssl::nid::Nid; + openssl::ec::EcGroup::from_curve_name(match key { + ec::Key::P224(_) => Nid::SECP224R1, + ec::Key::P256(_) => Nid::X9_62_PRIME256V1, + ec::Key::P384(_) => Nid::SECP384R1, + ec::Key::P521(_) => Nid::SECP521R1, + ec::Key::Ed25519(_) | ec::Key::X25519(_) => { + return Err(km_err!(UnsupportedEcCurve, "no NIST group for curve25519 key")) + } + }) + .map_err(openssl_err!("failed to determine EcGroup")) +} diff --git a/libs/rust/boringssl/src/eq.rs b/libs/rust/boringssl/src/eq.rs new file mode 100644 index 0000000..30c5142 --- /dev/null +++ b/libs/rust/boringssl/src/eq.rs @@ -0,0 +1,29 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of constant-time comparisons. +use kmr_common::crypto; + +/// Constant time comparator based on BoringSSL. +#[derive(Clone)] +pub struct BoringEq; + +impl crypto::ConstTimeEq for BoringEq { + fn eq(&self, left: &[u8], right: &[u8]) -> bool { + if left.len() != right.len() { + return false; + } + openssl::memcmp::eq(left, right) + } +} diff --git a/libs/rust/boringssl/src/err.rs b/libs/rust/boringssl/src/err.rs new file mode 100644 index 0000000..57c29a4 --- /dev/null +++ b/libs/rust/boringssl/src/err.rs @@ -0,0 +1,127 @@ +// Copyright 2022, 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. + +//! Error mapping functionality. + +use bssl_sys as ffi; +use core::convert::TryFrom; +use kmr_wire::keymint::ErrorCode; +use log::error; + +/// Map an OpenSSL `Error` into a KeyMint `ErrorCode` value. +pub(crate) fn map_openssl_err(err: &openssl::error::Error) -> ErrorCode { + let code = err.code(); + // Safety: no pointers involved. + let reason = unsafe { ffi::ERR_GET_REASON(code) }; + + // Global error reasons. + match reason { + ffi::ERR_R_MALLOC_FAILURE => return ErrorCode::MemoryAllocationFailed, + ffi::ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED + | ffi::ERR_R_PASSED_NULL_PARAMETER + | ffi::ERR_R_INTERNAL_ERROR + | ffi::ERR_R_OVERFLOW => return ErrorCode::BoringSslError, + _ => {} + } + + // SAFETY: `ERR_GET_LIB` is safe for all inputs. + match unsafe { ffi::ERR_GET_LIB(code) as u32 } { + ffi::ERR_LIB_USER => ErrorCode::try_from(reason).unwrap_or(ErrorCode::BoringSslError), + ffi::ERR_LIB_EVP => translate_evp_error(reason), + ffi::ERR_LIB_ASN1 => translate_asn1_error(reason), + ffi::ERR_LIB_CIPHER => translate_cipher_error(reason), + ffi::ERR_LIB_PKCS8 => translate_pkcs8_error(reason), + ffi::ERR_LIB_X509V3 => translate_x509v3_error(reason), + ffi::ERR_LIB_RSA => translate_rsa_error(reason), + _ => { + error!("unknown BoringSSL error code {}", code); + ErrorCode::BoringSslError + } + } +} + +fn translate_evp_error(reason: i32) -> ErrorCode { + match reason { + ffi::EVP_R_UNSUPPORTED_ALGORITHM + | ffi::EVP_R_OPERATON_NOT_INITIALIZED // NOTYPO: upstream typo + | ffi::EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE => ErrorCode::UnsupportedAlgorithm, + + ffi::EVP_R_BUFFER_TOO_SMALL + | ffi::EVP_R_EXPECTING_AN_RSA_KEY + | ffi::EVP_R_EXPECTING_A_DSA_KEY + | ffi::EVP_R_MISSING_PARAMETERS => ErrorCode::InvalidKeyBlob, + + ffi::EVP_R_DIFFERENT_PARAMETERS | ffi::EVP_R_DECODE_ERROR => ErrorCode::InvalidArgument, + + ffi::EVP_R_DIFFERENT_KEY_TYPES => ErrorCode::IncompatibleAlgorithm, + _ => ErrorCode::BoringSslError, + } +} + +fn translate_asn1_error(reason: i32) -> ErrorCode { + match reason { + ffi::ASN1_R_ENCODE_ERROR => ErrorCode::InvalidArgument, + _ => ErrorCode::BoringSslError, + } +} + +fn translate_cipher_error(reason: i32) -> ErrorCode { + match reason { + ffi::CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH + | ffi::CIPHER_R_WRONG_FINAL_BLOCK_LENGTH => ErrorCode::InvalidInputLength, + + ffi::CIPHER_R_UNSUPPORTED_KEY_SIZE | ffi::CIPHER_R_BAD_KEY_LENGTH => { + ErrorCode::UnsupportedKeySize + } + + ffi::CIPHER_R_BAD_DECRYPT => ErrorCode::InvalidArgument, + + ffi::CIPHER_R_INVALID_KEY_LENGTH => ErrorCode::InvalidKeyBlob, + _ => ErrorCode::BoringSslError, + } +} +fn translate_pkcs8_error(reason: i32) -> ErrorCode { + match reason { + ffi::PKCS8_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM | ffi::PKCS8_R_UNKNOWN_CIPHER => { + ErrorCode::UnsupportedAlgorithm + } + + ffi::PKCS8_R_PRIVATE_KEY_ENCODE_ERROR | ffi::PKCS8_R_PRIVATE_KEY_DECODE_ERROR => { + ErrorCode::InvalidKeyBlob + } + + ffi::PKCS8_R_ENCODE_ERROR => ErrorCode::InvalidArgument, + + _ => ErrorCode::BoringSslError, + } +} +fn translate_x509v3_error(reason: i32) -> ErrorCode { + match reason { + ffi::X509V3_R_UNKNOWN_OPTION => ErrorCode::UnsupportedAlgorithm, + + _ => ErrorCode::BoringSslError, + } +} +fn translate_rsa_error(reason: i32) -> ErrorCode { + match reason { + ffi::RSA_R_KEY_SIZE_TOO_SMALL => ErrorCode::IncompatiblePaddingMode, + ffi::RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE | ffi::RSA_R_DATA_TOO_SMALL_FOR_KEY_SIZE => { + ErrorCode::InvalidInputLength + } + ffi::RSA_R_DATA_TOO_LARGE_FOR_MODULUS | ffi::RSA_R_DATA_TOO_LARGE => { + ErrorCode::InvalidArgument + } + _ => ErrorCode::BoringSslError, + } +} diff --git a/libs/rust/boringssl/src/hmac.rs b/libs/rust/boringssl/src/hmac.rs new file mode 100644 index 0000000..3f8de9a --- /dev/null +++ b/libs/rust/boringssl/src/hmac.rs @@ -0,0 +1,132 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of HMAC. +use crate::types::HmacCtx; +use crate::{malloc_err, openssl_last_err}; +use Box; +use Vec; +#[cfg(soong)] +use bssl_sys as ffi; +use kmr_common::{crypto, crypto::OpaqueOr, explicit, km_err, vec_try, Error}; +use kmr_wire::keymint::Digest; +use log::error; + +/// [`crypto::Hmac`] implementation based on BoringSSL. +pub struct BoringHmac; + +impl crypto::Hmac for BoringHmac { + fn begin( + &self, + key: OpaqueOr, + digest: Digest, + ) -> Result, Error> { + let key = explicit!(key)?; + let op = BoringHmacOperation { + // Safety: BoringSSL emits either null or a valid raw pointer, and the value is + // immediately checked for null below. + ctx: unsafe { HmacCtx(ffi::HMAC_CTX_new()) }, + }; + if op.ctx.0.is_null() { + return Err(malloc_err!()); + } + + let digest = digest_into_openssl_ffi(digest)?; + #[cfg(soong)] + let key_len = key.0.len(); + #[cfg(not(soong))] + let key_len = key.0.len() as i32; + + // Safety: `op.ctx` is known non-null and valid, as is the result of + // `digest_into_openssl_ffi()`. `key_len` is length of `key.0`, which is a valid `Vec`. + let result = unsafe { + ffi::HMAC_Init_ex( + op.ctx.0, + key.0.as_ptr() as *const libc::c_void, + key_len, + digest, + core::ptr::null_mut(), + ) + }; + if result != 1 { + error!("Failed to HMAC_Init_ex()"); + return Err(openssl_last_err()); + } + Ok(Box::new(op)) + } +} + +/// HMAC operation based on BoringSSL. +/// +/// This implementation uses the `unsafe` wrappers around `HMAC_*` functions directly, because +/// BoringSSL does not support the `EVP_PKEY_HMAC` implementations that are used in the rust-openssl +/// crate. +pub struct BoringHmacOperation { + // Safety: `ctx` is always non-null and valid except for initial error path in `begin()` + ctx: HmacCtx, +} + +impl core::ops::Drop for BoringHmacOperation { + fn drop(&mut self) { + // Safety: `self.ctx` might be null (in the error path when `ffi::HMAC_CTX_new` fails) + // but `ffi::HMAC_CTX_free` copes with null. + unsafe { + ffi::HMAC_CTX_free(self.ctx.0); + } + } +} + +impl crypto::AccumulatingOperation for BoringHmacOperation { + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + // Safety: `self.ctx` is non-null and valid, and `data` is a valid slice. + let result = unsafe { ffi::HMAC_Update(self.ctx.0, data.as_ptr(), data.len()) }; + if result != 1 { + return Err(openssl_last_err()); + } + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + let mut output_len = ffi::EVP_MAX_MD_SIZE as u32; + let mut output = vec_try![0; ffi::EVP_MAX_MD_SIZE as usize]?; + + // Safety: `self.ctx` is non-null and valid; `output_len` is correct size of `output` + // buffer. + let result = unsafe { + // (force line break for safety lint limitation) + ffi::HMAC_Final(self.ctx.0, output.as_mut_ptr(), &mut output_len as *mut u32) + }; + if result != 1 { + return Err(openssl_last_err()); + } + output.truncate(output_len as usize); + Ok(output) + } +} + +/// Translate a [`keymint::Digest`] into a raw [`ffi::EVD_MD`]. +fn digest_into_openssl_ffi(digest: Digest) -> Result<*const ffi::EVP_MD, Error> { + // Safety: all of the `EVP_` functions return a non-null valid pointer. + unsafe { + match digest { + Digest::Md5 => Ok(ffi::EVP_md5()), + Digest::Sha1 => Ok(ffi::EVP_sha1()), + Digest::Sha224 => Ok(ffi::EVP_sha224()), + Digest::Sha256 => Ok(ffi::EVP_sha256()), + Digest::Sha384 => Ok(ffi::EVP_sha384()), + Digest::Sha512 => Ok(ffi::EVP_sha512()), + d => Err(km_err!(UnsupportedDigest, "unknown digest {:?}", d)), + } + } +} diff --git a/libs/rust/boringssl/src/lib.rs b/libs/rust/boringssl/src/lib.rs new file mode 100644 index 0000000..6684545 --- /dev/null +++ b/libs/rust/boringssl/src/lib.rs @@ -0,0 +1,160 @@ +// Copyright 2022, 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. + +//! Implementations of [`kmr_common::crypto`] traits based on BoringSSL. +extern crate alloc; + +use kmr_common::Error; +use kmr_wire::keymint::{Digest, ErrorCode}; +use log::error; +use openssl::hash::MessageDigest; + +pub mod aes_cmac; + +pub mod aes; +pub mod des; +pub mod ec; +pub mod eq; +pub mod hmac; +pub mod rng; +pub mod rsa; +pub mod sha256; + +#[cfg(soong)] +mod err; +#[cfg(soong)] +use err::*; + +#[cfg(test)] +mod tests; + +mod types; + +/// Map an OpenSSL `ErrorStack` into a KeyMint [`ErrorCode`] value. +pub(crate) fn map_openssl_errstack(errs: &openssl::error::ErrorStack) -> ErrorCode { + let errors = errs.errors(); + if errors.is_empty() { + error!("BoringSSL error requested but none available!"); + return ErrorCode::BoringSslError; + } + let err = &errors[0]; // safe: length checked above + map_openssl_err(err) +} + +/// Stub function for mapping an OpenSSL `ErrorStack` into a KeyMint [`ErrorCode`] value. +#[cfg(not(soong))] +fn map_openssl_err(_err: &openssl::error::Error) -> ErrorCode { + ErrorCode::BoringSslError +} + +/// Macro to auto-generate error mapping around invocations of `openssl` methods. +/// An invocation like: +/// +/// ```ignore +/// let x = ossl!(y.func(a, b))?; +/// ``` +/// +/// will map to: +/// +/// ```ignore +/// let x = y.func(a, b).map_err(openssl_err!("failed to perform: y.func(a, b)"))?; +/// ``` +#[macro_export] +macro_rules! ossl { + { $e:expr } => { + $e.map_err(openssl_err!(concat!("failed to perform: ", stringify!($e)))) + } +} + +/// Macro to emit a closure that builds an [`Error::Hal`] instance, based on an +/// openssl `ErrorStack` together with a format-like message. +#[macro_export] +macro_rules! openssl_err { + { $($arg:tt)+ } => { + |e| kmr_common::Error::Hal( + $crate::map_openssl_errstack(&e), + format!("{}:{}: {}: {:?}", file!(), line!(), format_args!($($arg)+), e) + ) + }; +} + +/// Macro to emit a closure that builds an [`Error::Hal`] instance, based on an openssl `ErrorStack` +/// together with a format-like message, plus default `ErrorCode` to be used if no OpenSSL error is +/// available. +#[macro_export] +macro_rules! openssl_err_or { + { $default:ident, $($arg:tt)+ } => { + |e| { + let errors = e.errors(); + let errcode = if errors.is_empty() { + kmr_wire::keymint::ErrorCode::$default + } else { + $crate::map_openssl_err(&errors[0]) // safe: length checked above + }; + kmr_common::Error::Hal( + errcode, + format!("{}:{}: {}: {:?}", file!(), line!(), format_args!($($arg)+), e) + ) + } + }; +} + +/// Macro to emit an [`Error`] indicating allocation failure at the current location. +#[macro_export] +macro_rules! malloc_err { + {} => { + kmr_common::Error::Alloc(concat!(file!(), ":", line!(), ": BoringSSL allocation failed")) + }; +} + +/// Translate the most recent OpenSSL error into [`Error`]. +fn openssl_last_err() -> Error { + from_openssl_err(openssl::error::ErrorStack::get()) +} + +/// Translate a returned `openssl` error into [`Error`]. +fn from_openssl_err(errs: openssl::error::ErrorStack) -> Error { + Error::Hal(map_openssl_errstack(&errs), "OpenSSL failure".to_string()) +} + +/// Translate a [`keymint::Digest`] into an OpenSSL [`MessageDigest`]. +fn digest_into_openssl(digest: Digest) -> Option { + match digest { + Digest::None => None, + Digest::Md5 => Some(MessageDigest::md5()), + Digest::Sha1 => Some(MessageDigest::sha1()), + Digest::Sha224 => Some(MessageDigest::sha224()), + Digest::Sha256 => Some(MessageDigest::sha256()), + Digest::Sha384 => Some(MessageDigest::sha384()), + Digest::Sha512 => Some(MessageDigest::sha512()), + } +} + +#[inline] +fn cvt_p(r: *mut T) -> Result<*mut T, Error> { + if r.is_null() { + Err(openssl_last_err()) + } else { + Ok(r) + } +} + +#[inline] +fn cvt(r: libc::c_int) -> Result { + if r <= 0 { + Err(openssl_last_err()) + } else { + Ok(r) + } +} diff --git a/libs/rust/boringssl/src/rng.rs b/libs/rust/boringssl/src/rng.rs new file mode 100644 index 0000000..88d672f --- /dev/null +++ b/libs/rust/boringssl/src/rng.rs @@ -0,0 +1,44 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of random number generation. +#[cfg(soong)] +use bssl_sys as ffi; +use kmr_common::crypto; + +/// [`crypto::Rng`] implementation based on BoringSSL. +#[derive(Default)] +pub struct BoringRng; + +impl crypto::Rng for BoringRng { + fn add_entropy(&mut self, data: &[u8]) { + #[cfg(soong)] + // Safety: `data` is a valid slice. + unsafe { + ffi::RAND_seed(data.as_ptr() as *const libc::c_void, data.len() as libc::c_int); + } + #[cfg(not(soong))] + // Safety: `data` is a valid slice. + unsafe { + ffi::RAND_add( + data.as_ptr() as *const libc::c_void, + data.len() as libc::c_int, + data.len() as f64, + ); + } + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + openssl::rand::rand_bytes(dest).unwrap(); // safe: BoringSSL's RAND_bytes() never fails + } +} diff --git a/libs/rust/boringssl/src/rsa.rs b/libs/rust/boringssl/src/rsa.rs new file mode 100644 index 0000000..bb0181d --- /dev/null +++ b/libs/rust/boringssl/src/rsa.rs @@ -0,0 +1,348 @@ +// Copyright 2022, 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. + +//! BoringSSL-based implementation of RSA. +use crate::types::{EvpMdCtx, EvpPkeyCtx}; +use crate::{cvt, cvt_p, digest_into_openssl, openssl_err, openssl_last_err, ossl}; +use Box; +use Vec; +#[cfg(soong)] +use bssl_sys as ffi; +use core::ptr; +use foreign_types::ForeignType; +use kmr_common::crypto::{ + rsa::{DecryptionMode, SignMode, PKCS1_UNDIGESTED_SIGNATURE_PADDING_OVERHEAD}, + OpaqueOr, +}; +use kmr_common::{crypto, explicit, km_err, vec_try, Error, FallibleAllocExt}; +use kmr_wire::{keymint, keymint::Digest, KeySizeInBits, RsaExponent}; +use openssl::hash::MessageDigest; + +/// Smallest allowed public exponent. +const MIN_RSA_EXPONENT: RsaExponent = RsaExponent(3); + +/// [`crypto::Rsa`] implementation based on BoringSSL. +pub struct BoringRsa { + /// Zero-sized private field to force use of [`default()`] for initialization. + _priv: core::marker::PhantomData<()>, +} + +impl core::default::Default for BoringRsa { + fn default() -> Self { + ffi::init(); + Self { _priv: core::marker::PhantomData } + } +} + +impl crypto::Rsa for BoringRsa { + fn generate_key( + &self, + _rng: &mut dyn crypto::Rng, + key_size: KeySizeInBits, + pub_exponent: RsaExponent, + _params: &[keymint::KeyParam], + ) -> Result { + // Reject some obviously-wrong parameter values. + if pub_exponent < MIN_RSA_EXPONENT { + return Err(km_err!( + InvalidArgument, + "Invalid public exponent, {:?} < {:?}", + pub_exponent, + MIN_RSA_EXPONENT + )); + } + if pub_exponent.0 % 2 != 1 { + return Err(km_err!( + InvalidArgument, + "Invalid public exponent {:?} (even number)", + pub_exponent + )); + } + let exponent = openssl::bn::BigNum::from_slice(&pub_exponent.0.to_be_bytes()[..]) + .map_err(openssl_err!("failed to create BigNum for exponent {:?}", pub_exponent))?; + + let rsa_key = + openssl::rsa::Rsa::generate_with_e(key_size.0, &exponent).map_err(openssl_err!( + "failed to generate RSA key size {:?} exponent {:?}", + key_size, + pub_exponent + ))?; + let asn1_data = ossl!(rsa_key.private_key_to_der())?; + Ok(crypto::KeyMaterial::Rsa(crypto::rsa::Key(asn1_data).into())) + } + + fn begin_decrypt( + &self, + key: OpaqueOr, + mode: DecryptionMode, + ) -> Result, Error> { + let key = explicit!(key)?; + let max_size = key.size(); + Ok(Box::new(BoringRsaDecryptOperation { key, mode, pending_input: Vec::new(), max_size })) + } + + fn begin_sign( + &self, + key: OpaqueOr, + mode: SignMode, + ) -> Result, Error> { + let key = explicit!(key)?; + let padding = match mode { + SignMode::NoPadding => openssl::rsa::Padding::NONE, + SignMode::Pkcs1_1_5Padding(_) => openssl::rsa::Padding::PKCS1, + SignMode::PssPadding(_) => openssl::rsa::Padding::PKCS1_PSS, + }; + + match mode { + SignMode::NoPadding | SignMode::Pkcs1_1_5Padding(Digest::None) => { + Ok(Box::new(BoringRsaUndigestSignOperation::new(key, mode)?)) + } + SignMode::Pkcs1_1_5Padding(digest) | SignMode::PssPadding(digest) => { + if let Some(digest) = digest_into_openssl(digest) { + Ok(Box::new(BoringRsaDigestSignOperation::new(key, mode, digest, padding)?)) + } else { + Err(km_err!(InvalidArgument, "no digest provided for mode {:?}", mode)) + } + } + } + } +} + +/// RSA decryption operation based on BoringSSL. +pub struct BoringRsaDecryptOperation { + key: crypto::rsa::Key, + mode: DecryptionMode, + pending_input: Vec, // Limited to size of key (`max_size` below). + max_size: usize, +} + +impl crypto::AccumulatingOperation for BoringRsaDecryptOperation { + fn max_input_size(&self) -> Option { + Some(self.max_size) + } + + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + self.pending_input.try_extend_from_slice(data)?; + Ok(()) + } + + fn finish(mut self: Box) -> Result, Error> { + let rsa_key = ossl!(openssl::rsa::Rsa::private_key_from_der(&self.key.0))?; + let priv_key = ossl!(openssl::pkey::PKey::from_rsa(rsa_key))?; + let mut decrypter = ossl!(openssl::encrypt::Decrypter::new(&priv_key))?; + + let padding = match self.mode { + DecryptionMode::NoPadding => openssl::rsa::Padding::NONE, + DecryptionMode::OaepPadding { msg_digest: _, mgf_digest: _ } => { + openssl::rsa::Padding::PKCS1_OAEP + } + DecryptionMode::Pkcs1_1_5Padding => openssl::rsa::Padding::PKCS1, + }; + decrypter + .set_rsa_padding(padding) + .map_err(openssl_err!("failed to create set_rsa_padding for {:?}", self.mode))?; + + if let DecryptionMode::OaepPadding { msg_digest, mgf_digest } = self.mode { + let omsg_digest = digest_into_openssl(msg_digest).ok_or_else(|| { + km_err!(UnsupportedDigest, "Digest::None not allowed for RSA-OAEP msg digest") + })?; + let omgf_digest = digest_into_openssl(mgf_digest).ok_or_else(|| { + km_err!(UnsupportedDigest, "Digest::None not allowed for RSA-OAEP MGF1 digest") + })?; + decrypter + .set_rsa_oaep_md(omsg_digest) + .map_err(openssl_err!("failed to set digest {:?}", msg_digest))?; + decrypter + .set_rsa_mgf1_md(omgf_digest) + .map_err(openssl_err!("failed to set MGF digest {:?}", mgf_digest))?; + } + + let buf_len = ossl!(decrypter.decrypt_len(&self.pending_input))?; + let mut output = vec_try![0; buf_len]?; + + if self.mode == DecryptionMode::NoPadding && self.pending_input.len() < buf_len { + self.pending_input = zero_pad_left(&self.pending_input, buf_len)?; + } + + let actual_len = ossl!(decrypter.decrypt(&self.pending_input, &mut output))?; + output.truncate(actual_len); + + Ok(output) + } +} + +/// RSA signing operation based on BoringSSL, for when an external digest is used. +/// Directly uses FFI functions because [`openssl::sign::Signer`] requires a lifetime. +pub struct BoringRsaDigestSignOperation { + // Safety: `pkey` internally holds a pointer to BoringSSL-allocated data (`EVP_PKEY`), + // as do both of the raw pointers. This means that this item stays valid under moves, + // because the FFI-allocated data doesn't move. + pkey: openssl::pkey::PKey, + + // Safety invariant: both `pctx` and `md_ctx` are non-`nullptr` (and valid and non-aliased) once + // item is constructed. + md_ctx: EvpMdCtx, + pctx: EvpPkeyCtx, +} + +impl Drop for BoringRsaDigestSignOperation { + fn drop(&mut self) { + // Safety: `md_ctx` is non-`nullptr` and valid due to invariant. `pctx` is owned by the + // `md_ctx`, so no need to explicitly free it. + unsafe { + ffi::EVP_MD_CTX_free(self.md_ctx.0); + } + } +} + +impl BoringRsaDigestSignOperation { + fn new( + key: crypto::rsa::Key, + mode: SignMode, + digest: MessageDigest, + padding: openssl::rsa::Padding, + ) -> Result { + let rsa_key = ossl!(openssl::rsa::Rsa::private_key_from_der(&key.0))?; + let pkey = ossl!(openssl::pkey::PKey::from_rsa(rsa_key))?; + + // Safety: all raw pointers are non-`nullptr` (and valid and non-aliasing) if returned + // without error from BoringSSL entrypoints. + unsafe { + let mut op = BoringRsaDigestSignOperation { + pkey, + md_ctx: EvpMdCtx(cvt_p(ffi::EVP_MD_CTX_new())?), + pctx: EvpPkeyCtx(ptr::null_mut()), + }; + + let r = ffi::EVP_DigestSignInit( + op.md_ctx.0, + &mut op.pctx.0, + digest.as_ptr(), + ptr::null_mut(), + op.pkey.as_ptr(), + ); + if r != 1 { + return Err(openssl_last_err()); + } + if op.pctx.0.is_null() { + return Err(km_err!(BoringSslError, "no PCTX!")); + } + + // Safety: `op.pctx` is not `nullptr` and is valid. + cvt(ffi::EVP_PKEY_CTX_set_rsa_padding(op.pctx.0, padding.as_raw()))?; + + if let SignMode::PssPadding(digest) = mode { + let digest_len = (kmr_common::tag::digest_len(digest)? / 8) as libc::c_int; + // Safety: `op.pctx` is not `nullptr` and is valid. + cvt(ffi::EVP_PKEY_CTX_set_rsa_pss_saltlen(op.pctx.0, digest_len))?; + } + + // Safety invariant: both `pctx` and `md_ctx` are non-`nullptr` and valid pointers on + // success. + Ok(op) + } + } +} + +impl crypto::AccumulatingOperation for BoringRsaDigestSignOperation { + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + // Safety: `data` is a valid slice, and `self.md_ctx` is non-`nullptr` and valid. + unsafe { + cvt(ffi::EVP_DigestUpdate(self.md_ctx.0, data.as_ptr() as *const _, data.len()))?; + } + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + let mut max_siglen = 0; + // Safety: `self.md_ctx` is non-`nullptr` and valid. + unsafe { + cvt(ffi::EVP_DigestSignFinal(self.md_ctx.0, ptr::null_mut(), &mut max_siglen))?; + } + let mut buf = vec_try![0; max_siglen]?; + let mut actual_siglen = max_siglen; + // Safety: `self.md_ctx` is non-`nullptr` and valid, and `buf` does have `actual_siglen` + // bytes. + unsafe { + cvt(ffi::EVP_DigestSignFinal( + self.md_ctx.0, + buf.as_mut_ptr() as *mut _, + &mut actual_siglen, + ))?; + } + buf.truncate(actual_siglen); + Ok(buf) + } +} + +/// RSA signing operation based on BoringSSL, for undigested data. +pub struct BoringRsaUndigestSignOperation { + rsa_key: openssl::rsa::Rsa, + left_pad: bool, + pending_input: Vec, + max_size: usize, +} + +impl BoringRsaUndigestSignOperation { + fn new(key: crypto::rsa::Key, mode: SignMode) -> Result { + let rsa_key = ossl!(openssl::rsa::Rsa::private_key_from_der(&key.0))?; + let (left_pad, max_size) = match mode { + SignMode::NoPadding => (true, rsa_key.size() as usize), + SignMode::Pkcs1_1_5Padding(Digest::None) => { + (false, (rsa_key.size() as usize) - PKCS1_UNDIGESTED_SIGNATURE_PADDING_OVERHEAD) + } + _ => return Err(km_err!(UnsupportedPaddingMode, "sign undigested mode {:?}", mode)), + }; + Ok(Self { rsa_key, left_pad, pending_input: Vec::new(), max_size }) + } +} + +impl crypto::AccumulatingOperation for BoringRsaUndigestSignOperation { + fn max_input_size(&self) -> Option { + Some(self.max_size) + } + + fn update(&mut self, data: &[u8]) -> Result<(), Error> { + // OK to accumulate data as there is a size limit. + self.pending_input.try_extend_from_slice(data)?; + Ok(()) + } + + fn finish(self: Box) -> Result, Error> { + let mut buf = vec_try![0; self.rsa_key.size() as usize]?; + if self.left_pad { + let padded_input = zero_pad_left(&self.pending_input, self.max_size)?; + ossl!(self.rsa_key.private_encrypt( + &padded_input, + &mut buf, + openssl::rsa::Padding::NONE + ))?; + } else { + ossl!(self.rsa_key.private_encrypt( + &self.pending_input, + &mut buf, + openssl::rsa::Padding::PKCS1 + ))?; + } + + Ok(buf) + } +} + +fn zero_pad_left(data: &[u8], len: usize) -> Result, Error> { + let mut dest = vec_try![0; len]?; + let padding_len = len - data.len(); + dest[padding_len..].copy_from_slice(data); + Ok(dest) +} diff --git a/libs/rust/boringssl/src/sha256.rs b/libs/rust/boringssl/src/sha256.rs new file mode 100644 index 0000000..3f0ae86 --- /dev/null +++ b/libs/rust/boringssl/src/sha256.rs @@ -0,0 +1,27 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This crate implements the IKeystoreSecurityLevel interface. + +//! BoringSSL-based implementation of SHA-256. +use kmr_common::{crypto, Error}; + +/// [`crypto::Sha256`] implementation based on BoringSSL. +pub struct BoringSha256; + +impl crypto::Sha256 for BoringSha256 { + fn hash(&self, data: &[u8]) -> Result<[u8; 32], Error> { + Ok(openssl::sha::sha256(data)) + } +} diff --git a/libs/rust/boringssl/src/tests.rs b/libs/rust/boringssl/src/tests.rs new file mode 100644 index 0000000..1bce1a4 --- /dev/null +++ b/libs/rust/boringssl/src/tests.rs @@ -0,0 +1,67 @@ +// Copyright 2022, 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. + +use super::*; + +// Inject the BoringSSL-based implementations of crypto traits into the smoke tests from +// `kmr_tests`. + +#[test] +fn test_rng() { + let mut rng = rng::BoringRng; + kmr_tests::test_rng(&mut rng); +} + +#[test] +fn test_eq() { + let comparator = eq::BoringEq; + kmr_tests::test_eq(comparator); +} + +#[test] +fn test_hkdf() { + kmr_tests::test_hkdf(hmac::BoringHmac {}); +} + +#[test] +fn test_hmac() { + kmr_tests::test_hmac(hmac::BoringHmac {}); +} + +#[cfg(soong)] +#[test] +fn test_aes_cmac() { + kmr_tests::test_aes_cmac(aes_cmac::BoringAesCmac {}); +} + +#[cfg(soong)] +#[test] +fn test_ckdf() { + kmr_tests::test_ckdf(aes_cmac::BoringAesCmac {}); +} + +#[test] +fn test_aes_gcm() { + kmr_tests::test_aes_gcm(aes::BoringAes {}); +} + +#[test] +fn test_des() { + kmr_tests::test_des(des::BoringDes {}); +} + +#[test] +fn test_sha256() { + kmr_tests::test_sha256(sha256::BoringSha256 {}); +} diff --git a/libs/rust/boringssl/src/types.rs b/libs/rust/boringssl/src/types.rs new file mode 100644 index 0000000..f07c0cf --- /dev/null +++ b/libs/rust/boringssl/src/types.rs @@ -0,0 +1,50 @@ +// Copyright 2022, 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. + +//! Rust types definitions for BoringSSL objects. +#[cfg(soong)] +use bssl_sys as ffi; + +/// New type for `*mut ffi::CMAC_CTX` to implement `Send` for it. This allow us to still check if a +/// `!Send` item is added to `BoringAesCmacOperation` +#[allow(dead_code)] +pub(crate) struct CmacCtx(pub(crate) *mut ffi::CMAC_CTX); + +// Safety: Checked CMAC_CTX allocation, initialization and destruction code to insure that it is +// safe to share it between threads. +unsafe impl Send for CmacCtx {} + +/// New type for `*mut ffi::EVP_MD_CTX` to implement `Send` for it. This allow us to still check if +/// a `!Send` item is added to `BoringEcDigestSignOperation` +pub(crate) struct EvpMdCtx(pub(crate) *mut ffi::EVP_MD_CTX); + +// Safety: Checked EVP_MD_CTX allocation, initialization and destruction code to insure that it is +// safe to share it between threads. +unsafe impl Send for EvpMdCtx {} + +/// New type for `*mut ffi::EVP_PKEY_CTX` to implement `Send` for it. This allow us to still check +/// if a `!Send` item is added to `BoringEcDigestSignOperation` +pub(crate) struct EvpPkeyCtx(pub(crate) *mut ffi::EVP_PKEY_CTX); + +// Safety: Checked EVP_MD_CTX allocation, initialization and destruction code to insure that it is +// safe to share it between threads. +unsafe impl Send for EvpPkeyCtx {} + +/// New type for `*mut ffi::HMAC_CTX` to implement `Send` for it. This allow us to still check if +/// a `!Send` item is added to `BoringHmacOperation` +pub(crate) struct HmacCtx(pub(crate) *mut ffi::HMAC_CTX); + +// Safety: Checked EVP_MD_CTX allocation, initialization and destruction code to insure that it is +// safe to share it between threads. +unsafe impl Send for HmacCtx {} diff --git a/libs/rust/build.rs b/libs/rust/build.rs new file mode 100644 index 0000000..5025cc6 --- /dev/null +++ b/libs/rust/build.rs @@ -0,0 +1,24 @@ +use std::fs; + +fn main() { + let out_dir = "src/proto"; + + fs::create_dir_all(out_dir).unwrap(); + + prost_build::Config::new() + .out_dir(out_dir) + .compile_protos( + &["proto/storage.proto"], + &["proto/"], + ) + .expect("Failed to compile .proto files"); + + let mod_file = format!("{}/mod.rs", out_dir); + let mod_content = ["storage.rs"] + .iter() + .map(|file| format!("pub mod {};", file.trim_end_matches(".rs"))) + .collect::>() + .join("\n"); + + std::fs::write(&mod_file, mod_content).unwrap(); +} \ No newline at end of file diff --git a/libs/rust/common/Cargo.toml b/libs/rust/common/Cargo.toml new file mode 100644 index 0000000..6e086c8 --- /dev/null +++ b/libs/rust/common/Cargo.toml @@ -0,0 +1,28 @@ +# Note that Cargo is not an officially supported build tool (Android's Soong is the official +# tool). This Cargo.toml file is included purely for the convenience of KeyMint developers. + +[package] +name = "kmr-common" +version = "0.1.0" +authors = ["David Drysdale "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +cddl-cat = { version = "^0.6.1", optional = true } +ciborium = { version = "^0.2.0", default-features = false } +ciborium-io = "^0.2.0" +coset = "0.3.3" +der = { version = "^0.7.2", features = ["alloc", "derive"] } +enumn = "0.1.4" +kmr-derive = "*" +kmr-wire = "*" +log = "^0.4" +pkcs1 = { version = "^0.7.5", features = ["alloc"] } +pkcs8 = "^0.10.2" +sec1 = { version = "0.7.3", features = ["alloc", "der", "pkcs8"] } +spki = { version = "0.7.3"} +zeroize = { version = "^1.5.6", features = ["alloc", "zeroize_derive"] } + +[dev-dependencies] +hex = "0.4.3" diff --git a/libs/rust/common/fuzz/.gitignore b/libs/rust/common/fuzz/.gitignore new file mode 100644 index 0000000..a092511 --- /dev/null +++ b/libs/rust/common/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/libs/rust/common/fuzz/Cargo.toml b/libs/rust/common/fuzz/Cargo.toml new file mode 100644 index 0000000..8839a6a --- /dev/null +++ b/libs/rust/common/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "kmr-common-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +kmr-wire = "*" + +[dependencies.kmr-common] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "keyblob" +path = "fuzz_targets/keyblob.rs" +test = false +doc = false + +[patch.crates-io] +kmr-derive = { path = "../../derive" } +kmr-wire = { path = "../../wire" } diff --git a/libs/rust/common/fuzz/fuzz_targets/keyblob.rs b/libs/rust/common/fuzz/fuzz_targets/keyblob.rs new file mode 100644 index 0000000..8a3068b --- /dev/null +++ b/libs/rust/common/fuzz/fuzz_targets/keyblob.rs @@ -0,0 +1,24 @@ +// Copyright 2022, 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. + +//! Fuzzer for keyblob parsing. + +#![no_main] +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // `data` allegedly holds a CBOR-serialized keyblob that has arrived from userspace. Do we + // trust it? I don't think so... + let _ = kmr_common::keyblob::EncryptedKeyBlob::new(data); +}); diff --git a/libs/rust/common/generated.cddl b/libs/rust/common/generated.cddl new file mode 100644 index 0000000..01e42db --- /dev/null +++ b/libs/rust/common/generated.cddl @@ -0,0 +1,638 @@ +DateTime = int +KeySizeInBits = int +RsaExponent = int +Algorithm = &( + Algorithm_Rsa: 1, + Algorithm_Ec: 3, + Algorithm_Aes: 32, + Algorithm_TripleDes: 33, + Algorithm_Hmac: 128, +) +BlockMode = &( + BlockMode_Ecb: 1, + BlockMode_Cbc: 2, + BlockMode_Ctr: 3, + BlockMode_Gcm: 32, +) +Digest = &( + Digest_None: 0, + Digest_Md5: 1, + Digest_Sha1: 2, + Digest_Sha224: 3, + Digest_Sha256: 4, + Digest_Sha384: 5, + Digest_Sha512: 6, +) +EcCurve = &( + EcCurve_P224: 0, + EcCurve_P256: 1, + EcCurve_P384: 2, + EcCurve_P521: 3, + EcCurve_Curve25519: 4, +) +CurveType = &( + CurveType_Nist: 0, + CurveType_EdDsa: 1, + CurveType_Xdh: 2, +) +ErrorCode = &( + ErrorCode_Ok: 0, + ErrorCode_RootOfTrustAlreadySet: -1, + ErrorCode_UnsupportedPurpose: -2, + ErrorCode_IncompatiblePurpose: -3, + ErrorCode_UnsupportedAlgorithm: -4, + ErrorCode_IncompatibleAlgorithm: -5, + ErrorCode_UnsupportedKeySize: -6, + ErrorCode_UnsupportedBlockMode: -7, + ErrorCode_IncompatibleBlockMode: -8, + ErrorCode_UnsupportedMacLength: -9, + ErrorCode_UnsupportedPaddingMode: -10, + ErrorCode_IncompatiblePaddingMode: -11, + ErrorCode_UnsupportedDigest: -12, + ErrorCode_IncompatibleDigest: -13, + ErrorCode_InvalidExpirationTime: -14, + ErrorCode_InvalidUserId: -15, + ErrorCode_InvalidAuthorizationTimeout: -16, + ErrorCode_UnsupportedKeyFormat: -17, + ErrorCode_IncompatibleKeyFormat: -18, + ErrorCode_UnsupportedKeyEncryptionAlgorithm: -19, + ErrorCode_UnsupportedKeyVerificationAlgorithm: -20, + ErrorCode_InvalidInputLength: -21, + ErrorCode_KeyExportOptionsInvalid: -22, + ErrorCode_DelegationNotAllowed: -23, + ErrorCode_KeyNotYetValid: -24, + ErrorCode_KeyExpired: -25, + ErrorCode_KeyUserNotAuthenticated: -26, + ErrorCode_OutputParameterNull: -27, + ErrorCode_InvalidOperationHandle: -28, + ErrorCode_InsufficientBufferSpace: -29, + ErrorCode_VerificationFailed: -30, + ErrorCode_TooManyOperations: -31, + ErrorCode_UnexpectedNullPointer: -32, + ErrorCode_InvalidKeyBlob: -33, + ErrorCode_ImportedKeyNotEncrypted: -34, + ErrorCode_ImportedKeyDecryptionFailed: -35, + ErrorCode_ImportedKeyNotSigned: -36, + ErrorCode_ImportedKeyVerificationFailed: -37, + ErrorCode_InvalidArgument: -38, + ErrorCode_UnsupportedTag: -39, + ErrorCode_InvalidTag: -40, + ErrorCode_MemoryAllocationFailed: -41, + ErrorCode_ImportParameterMismatch: -44, + ErrorCode_SecureHwAccessDenied: -45, + ErrorCode_OperationCancelled: -46, + ErrorCode_ConcurrentAccessConflict: -47, + ErrorCode_SecureHwBusy: -48, + ErrorCode_SecureHwCommunicationFailed: -49, + ErrorCode_UnsupportedEcField: -50, + ErrorCode_MissingNonce: -51, + ErrorCode_InvalidNonce: -52, + ErrorCode_MissingMacLength: -53, + ErrorCode_KeyRateLimitExceeded: -54, + ErrorCode_CallerNonceProhibited: -55, + ErrorCode_KeyMaxOpsExceeded: -56, + ErrorCode_InvalidMacLength: -57, + ErrorCode_MissingMinMacLength: -58, + ErrorCode_UnsupportedMinMacLength: -59, + ErrorCode_UnsupportedKdf: -60, + ErrorCode_UnsupportedEcCurve: -61, + ErrorCode_KeyRequiresUpgrade: -62, + ErrorCode_AttestationChallengeMissing: -63, + ErrorCode_KeymintNotConfigured: -64, + ErrorCode_AttestationApplicationIdMissing: -65, + ErrorCode_CannotAttestIds: -66, + ErrorCode_RollbackResistanceUnavailable: -67, + ErrorCode_HardwareTypeUnavailable: -68, + ErrorCode_ProofOfPresenceRequired: -69, + ErrorCode_ConcurrentProofOfPresenceRequested: -70, + ErrorCode_NoUserConfirmation: -71, + ErrorCode_DeviceLocked: -72, + ErrorCode_EarlyBootEnded: -73, + ErrorCode_AttestationKeysNotProvisioned: -74, + ErrorCode_AttestationIdsNotProvisioned: -75, + ErrorCode_InvalidOperation: -76, + ErrorCode_StorageKeyUnsupported: -77, + ErrorCode_IncompatibleMgfDigest: -78, + ErrorCode_UnsupportedMgfDigest: -79, + ErrorCode_MissingNotBefore: -80, + ErrorCode_MissingNotAfter: -81, + ErrorCode_MissingIssuerSubject: -82, + ErrorCode_InvalidIssuerSubject: -83, + ErrorCode_BootLevelExceeded: -84, + ErrorCode_HardwareNotYetAvailable: -85, + ErrorCode_ModuleHashAlreadySet: -86, + ErrorCode_Unimplemented: -100, + ErrorCode_VersionMismatch: -101, + ErrorCode_UnknownError: -1000, + ErrorCode_EncodingError: -20000, + ErrorCode_BoringSslError: -30000, +) +HardwareAuthenticatorType = &( + HardwareAuthenticatorType_None: 0, + HardwareAuthenticatorType_Password: 1, + HardwareAuthenticatorType_Fingerprint: 2, + HardwareAuthenticatorType_Any: -1, +) +KeyFormat = &( + KeyFormat_X509: 0, + KeyFormat_Pkcs8: 1, + KeyFormat_Raw: 3, +) +KeyOrigin = &( + KeyOrigin_Generated: 0, + KeyOrigin_Derived: 1, + KeyOrigin_Imported: 2, + KeyOrigin_Reserved: 3, + KeyOrigin_SecurelyImported: 4, +) +KeyPurpose = &( + KeyPurpose_Encrypt: 0, + KeyPurpose_Decrypt: 1, + KeyPurpose_Sign: 2, + KeyPurpose_Verify: 3, + KeyPurpose_WrapKey: 5, + KeyPurpose_AgreeKey: 6, + KeyPurpose_AttestKey: 7, +) +PaddingMode = &( + PaddingMode_None: 1, + PaddingMode_RsaOaep: 2, + PaddingMode_RsaPss: 3, + PaddingMode_RsaPkcs115Encrypt: 4, + PaddingMode_RsaPkcs115Sign: 5, + PaddingMode_Pkcs7: 64, +) +SecurityLevel = &( + SecurityLevel_Software: 0, + SecurityLevel_TrustedEnvironment: 1, + SecurityLevel_Strongbox: 2, + SecurityLevel_Keystore: 100, +) +Tag = &( + Tag_Invalid: 0, + Tag_Purpose: 536870913, + Tag_Algorithm: 268435458, + Tag_KeySize: 805306371, + Tag_BlockMode: 536870916, + Tag_Digest: 536870917, + Tag_Padding: 536870918, + Tag_CallerNonce: 1879048199, + Tag_MinMacLength: 805306376, + Tag_EcCurve: 268435466, + Tag_RsaPublicExponent: 1342177480, + Tag_IncludeUniqueId: 1879048394, + Tag_RsaOaepMgfDigest: 536871115, + Tag_BootloaderOnly: 1879048494, + Tag_RollbackResistance: 1879048495, + Tag_HardwareType: 268435760, + Tag_EarlyBootOnly: 1879048497, + Tag_ActiveDatetime: 1610613136, + Tag_OriginationExpireDatetime: 1610613137, + Tag_UsageExpireDatetime: 1610613138, + Tag_MinSecondsBetweenOps: 805306771, + Tag_MaxUsesPerBoot: 805306772, + Tag_UsageCountLimit: 805306773, + Tag_UserId: 805306869, + Tag_UserSecureId: -1610612234, + Tag_NoAuthRequired: 1879048695, + Tag_UserAuthType: 268435960, + Tag_AuthTimeout: 805306873, + Tag_AllowWhileOnBody: 1879048698, + Tag_TrustedUserPresenceRequired: 1879048699, + Tag_TrustedConfirmationRequired: 1879048700, + Tag_UnlockedDeviceRequired: 1879048701, + Tag_ApplicationId: -1879047591, + Tag_ApplicationData: -1879047492, + Tag_CreationDatetime: 1610613437, + Tag_Origin: 268436158, + Tag_RootOfTrust: -1879047488, + Tag_OsVersion: 805307073, + Tag_OsPatchlevel: 805307074, + Tag_UniqueId: -1879047485, + Tag_AttestationChallenge: -1879047484, + Tag_AttestationApplicationId: -1879047483, + Tag_AttestationIdBrand: -1879047482, + Tag_AttestationIdDevice: -1879047481, + Tag_AttestationIdProduct: -1879047480, + Tag_AttestationIdSerial: -1879047479, + Tag_AttestationIdImei: -1879047478, + Tag_AttestationIdMeid: -1879047477, + Tag_AttestationIdManufacturer: -1879047476, + Tag_AttestationIdModel: -1879047475, + Tag_VendorPatchlevel: 805307086, + Tag_BootPatchlevel: 805307087, + Tag_DeviceUniqueAttestation: 1879048912, + Tag_IdentityCredentialKey: 1879048913, + Tag_StorageKey: 1879048914, + Tag_AttestationIdSecondImei: -1879047469, + Tag_AssociatedData: -1879047192, + Tag_Nonce: -1879047191, + Tag_MacLength: 805307371, + Tag_ResetSinceIdRotation: 1879049196, + Tag_ConfirmationToken: -1879047187, + Tag_CertificateSerial: -2147482642, + Tag_CertificateSubject: -1879047185, + Tag_CertificateNotBefore: 1610613744, + Tag_CertificateNotAfter: 1610613745, + Tag_MaxBootLevel: 805307378, + Tag_ModuleHash: -1879047468, +) +TagType = &( + TagType_Invalid: 0, + TagType_Enum: 268435456, + TagType_EnumRep: 536870912, + TagType_Uint: 805306368, + TagType_UintRep: 1073741824, + TagType_Ulong: 1342177280, + TagType_Date: 1610612736, + TagType_Bool: 1879048192, + TagType_Bignum: -2147483648, + TagType_Bytes: -1879048192, + TagType_UlongRep: -1610612736, +) +AttestationKey = [ + key_blob: bstr, + attest_key_params: [* KeyParam], + issuer_subject_name: bstr, +] +Certificate = [ + encoded_certificate: bstr, +] +DeviceInfo = [ + device_info: bstr, +] +HardwareAuthToken = [ + challenge: int, + user_id: int, + authenticator_id: int, + authenticator_type: HardwareAuthenticatorType, + timestamp: Timestamp, + mac: bstr, +] +KeyCharacteristics = [ + security_level: SecurityLevel, + authorizations: [* KeyParam], +] +KeyCreationResult = [ + key_blob: bstr, + key_characteristics: [* KeyCharacteristics], + certificate_chain: [* Certificate], +] +KeyMintHardwareInfo = [ + version_number: int, + security_level: SecurityLevel, + key_mint_name: tstr, + key_mint_author_name: tstr, + timestamp_token_required: bool, +] +EekCurve = &( + EekCurve_None: 0, + EekCurve_P256: 1, + EekCurve_Curve25519: 2, +) +MacedPublicKey = [ + maced_key: bstr, +] +ProtectedData = [ + protected_data: bstr, +] +HardwareInfo = [ + version_number: int, + rpc_author_name: tstr, + supported_eek_curve: EekCurve, + unique_id: [? tstr], + supported_num_keys_in_csr: int, +] +TimeStampToken = [ + challenge: int, + timestamp: Timestamp, + mac: bstr, +] +Timestamp = [ + milliseconds: int, +] +SharedSecretParameters = [ + seed: bstr, + nonce: bstr, +] +KeyParam = &( + [268435458, Algorithm], ; Tag_Algorithm + [536870916, BlockMode], ; Tag_BlockMode + [536870918, PaddingMode], ; Tag_Padding + [536870917, Digest], ; Tag_Digest + [268435466, EcCurve], ; Tag_EcCurve + [268436158, KeyOrigin], ; Tag_Origin + [536870913, KeyPurpose], ; Tag_Purpose + [805306371, KeySizeInBits], ; Tag_KeySize + [1879048199, bstr], ; Tag_CallerNonce + [805306376, int], ; Tag_MinMacLength + [1342177480, RsaExponent], ; Tag_RsaPublicExponent + [1879048394, true], ; Tag_IncludeUniqueId + [536871115, Digest], ; Tag_RsaOaepMgfDigest + [1879048494, true], ; Tag_BootloaderOnly + [1879048495, true], ; Tag_RollbackResistance + [1879048497, true], ; Tag_EarlyBootOnly + [1610613136, DateTime], ; Tag_ActiveDatetime + [1610613137, DateTime], ; Tag_OriginationExpireDatetime + [1610613138, DateTime], ; Tag_UsageExpireDatetime + [805306772, int], ; Tag_MaxUsesPerBoot + [805306773, int], ; Tag_UsageCountLimit + [805306869, int], ; Tag_UserId + [-1610612234, int], ; Tag_UserSecureId + [1879048695, true], ; Tag_NoAuthRequired + [268435960, int], ; Tag_UserAuthType + [805306873, int], ; Tag_AuthTimeout + [1879048698, true], ; Tag_AllowWhileOnBody + [1879048699, true], ; Tag_TrustedUserPresenceRequired + [1879048700, true], ; Tag_TrustedConfirmationRequired + [1879048701, true], ; Tag_UnlockedDeviceRequired + [-1879047591, bstr], ; Tag_ApplicationId + [-1879047492, bstr], ; Tag_ApplicationData + [1610613437, DateTime], ; Tag_CreationDatetime + [-1879047488, bstr], ; Tag_RootOfTrust + [805307073, int], ; Tag_OsVersion + [805307074, int], ; Tag_OsPatchlevel + [-1879047484, bstr], ; Tag_AttestationChallenge + [-1879047483, bstr], ; Tag_AttestationApplicationId + [-1879047482, bstr], ; Tag_AttestationIdBrand + [-1879047481, bstr], ; Tag_AttestationIdDevice + [-1879047480, bstr], ; Tag_AttestationIdProduct + [-1879047479, bstr], ; Tag_AttestationIdSerial + [-1879047478, bstr], ; Tag_AttestationIdImei + [-1879047469, bstr], ; Tag_AttestationIdSecondImei + [-1879047477, bstr], ; Tag_AttestationIdMeid + [-1879047476, bstr], ; Tag_AttestationIdManufacturer + [-1879047475, bstr], ; Tag_AttestationIdModel + [805307086, int], ; Tag_VendorPatchlevel + [805307087, int], ; Tag_BootPatchlevel + [1879048912, true], ; Tag_DeviceUniqueAttestation + [1879048914, true], ; Tag_StorageKey + [-1879047191, bstr], ; Tag_Nonce + [805307371, int], ; Tag_MacLength + [1879049196, true], ; Tag_ResetSinceIdRotation + [-2147482642, bstr], ; Tag_CertificateSerial + [-1879047185, bstr], ; Tag_CertificateSubject + [1610613744, DateTime], ; Tag_CertificateNotBefore + [1610613745, DateTime], ; Tag_CertificateNotAfter + [805307378, int], ; Tag_MaxBootLevel + [-1879047468, bstr], ; Tag_ModuleHash +) +KeyMintOperation = &( + DeviceGetHardwareInfo: 0x11, + DeviceAddRngEntropy: 0x12, + DeviceGenerateKey: 0x13, + DeviceImportKey: 0x14, + DeviceImportWrappedKey: 0x15, + DeviceUpgradeKey: 0x16, + DeviceDeleteKey: 0x17, + DeviceDeleteAllKeys: 0x18, + DeviceDestroyAttestationIds: 0x19, + DeviceBegin: 0x1a, + DeviceEarlyBootEnded: 0x1c, + DeviceConvertStorageKeyToEphemeral: 0x1d, + DeviceGetKeyCharacteristics: 0x1e, + OperationUpdateAad: 0x31, + OperationUpdate: 0x32, + OperationFinish: 0x33, + OperationAbort: 0x34, + RpcGetHardwareInfo: 0x41, + RpcGenerateEcdsaP256KeyPair: 0x42, + RpcGenerateCertificateRequest: 0x43, + RpcGenerateCertificateV2Request: 0x44, + SharedSecretGetSharedSecretParameters: 0x51, + SharedSecretComputeSharedSecret: 0x52, + SecureClockGenerateTimeStamp: 0x61, + GetRootOfTrustChallenge: 0x71, + GetRootOfTrust: 0x72, + SendRootOfTrust: 0x73, + SetHalInfo: 0x81, + SetBootInfo: 0x82, + SetAttestationIds: 0x83, + SetHalVersion: 0x84, + SetAdditionalAttestationInfo: 0x91, +) +GetHardwareInfoRequest = [] +GetHardwareInfoResponse = [ + ret: KeyMintHardwareInfo, +] +AddRngEntropyRequest = [ + data: bstr, +] +AddRngEntropyResponse = [] +GenerateKeyRequest = [ + key_params: [* KeyParam], + attestation_key: [? AttestationKey], +] +GenerateKeyResponse = [ + ret: KeyCreationResult, +] +ImportKeyRequest = [ + key_params: [* KeyParam], + key_format: KeyFormat, + key_data: bstr, + attestation_key: [? AttestationKey], +] +ImportKeyResponse = [ + ret: KeyCreationResult, +] +ImportWrappedKeyRequest = [ + wrapped_key_data: bstr, + wrapping_key_blob: bstr, + masking_key: bstr, + unwrapping_params: [* KeyParam], + password_sid: int, + biometric_sid: int, +] +ImportWrappedKeyResponse = [ + ret: KeyCreationResult, +] +UpgradeKeyRequest = [ + key_blob_to_upgrade: bstr, + upgrade_params: [* KeyParam], +] +UpgradeKeyResponse = [ + ret: bstr, +] +DeleteKeyRequest = [ + key_blob: bstr, +] +DeleteKeyResponse = [] +DeleteAllKeysRequest = [] +DeleteAllKeysResponse = [] +DestroyAttestationIdsRequest = [] +DestroyAttestationIdsResponse = [] +BeginRequest = [ + purpose: KeyPurpose, + key_blob: bstr, + params: [* KeyParam], + auth_token: [? HardwareAuthToken], +] +InternalBeginResult = [ + challenge: int, + params: [* KeyParam], + op_handle: int, +] +EarlyBootEndedRequest = [] +EarlyBootEndedResponse = [] +ConvertStorageKeyToEphemeralRequest = [ + storage_key_blob: bstr, +] +ConvertStorageKeyToEphemeralResponse = [ + ret: bstr, +] +GetKeyCharacteristicsRequest = [ + key_blob: bstr, + app_id: bstr, + app_data: bstr, +] +GetKeyCharacteristicsResponse = [ + ret: [* KeyCharacteristics], +] +UpdateAadRequest = [ + op_handle: int, + input: bstr, + auth_token: [? HardwareAuthToken], + timestamp_token: [? TimeStampToken], +] +UpdateAadResponse = [] +UpdateRequest = [ + op_handle: int, + input: bstr, + auth_token: [? HardwareAuthToken], + timestamp_token: [? TimeStampToken], +] +UpdateResponse = [ + ret: bstr, +] +FinishRequest = [ + op_handle: int, + input: [? bstr], + signature: [? bstr], + auth_token: [? HardwareAuthToken], + timestamp_token: [? TimeStampToken], + confirmation_token: [? bstr], +] +FinishResponse = [ + ret: bstr, +] +AbortRequest = [ + op_handle: int, +] +AbortResponse = [] +GetRpcHardwareInfoRequest = [] +GetRpcHardwareInfoResponse = [ + ret: HardwareInfo, +] +GenerateEcdsaP256KeyPairRequest = [ + test_mode: bool, +] +GenerateEcdsaP256KeyPairResponse = [ + maced_public_key: MacedPublicKey, + ret: bstr, +] +GenerateCertificateRequestRequest = [ + test_mode: bool, + keys_to_sign: [* MacedPublicKey], + endpoint_encryption_cert_chain: bstr, + challenge: bstr, +] +GenerateCertificateRequestResponse = [ + device_info: DeviceInfo, + protected_data: ProtectedData, + ret: bstr, +] +GenerateCertificateRequestV2Request = [ + keys_to_sign: [* MacedPublicKey], + challenge: bstr, +] +GenerateCertificateRequestV2Response = [ + ret: bstr, +] +GetSharedSecretParametersRequest = [] +GetSharedSecretParametersResponse = [ + ret: SharedSecretParameters, +] +ComputeSharedSecretRequest = [ + params: [* SharedSecretParameters], +] +ComputeSharedSecretResponse = [ + ret: bstr, +] +GenerateTimeStampRequest = [ + challenge: int, +] +GenerateTimeStampResponse = [ + ret: TimeStampToken, +] +PerformOpReq = &( + [DeviceGetHardwareInfo, GetHardwareInfoRequest], + [DeviceAddRngEntropy, AddRngEntropyRequest], + [DeviceGenerateKey, GenerateKeyRequest], + [DeviceImportKey, ImportKeyRequest], + [DeviceImportWrappedKey, ImportWrappedKeyRequest], + [DeviceUpgradeKey, UpgradeKeyRequest], + [DeviceDeleteKey, DeleteKeyRequest], + [DeviceDeleteAllKeys, DeleteAllKeysRequest], + [DeviceDestroyAttestationIds, DestroyAttestationIdsRequest], + [DeviceBegin, BeginRequest], + [DeviceEarlyBootEnded, EarlyBootEndedRequest], + [DeviceConvertStorageKeyToEphemeral, ConvertStorageKeyToEphemeralRequest], + [DeviceGetKeyCharacteristics, GetKeyCharacteristicsRequest], + [OperationUpdateAad, UpdateAadRequest], + [OperationUpdate, UpdateRequest], + [OperationFinish, FinishRequest], + [OperationAbort, AbortRequest], + [RpcGetHardwareInfo, GetRpcHardwareInfoRequest], + [RpcGenerateEcdsaP256KeyPair, GenerateEcdsaP256KeyPairRequest], + [RpcGenerateCertificateRequest, GenerateCertificateRequestRequest], + [RpcGenerateCertificateV2Request, GenerateCertificateRequestV2Request], + [SharedSecretGetSharedSecretParameters, GetSharedSecretParametersRequest], + [SharedSecretComputeSharedSecret, ComputeSharedSecretRequest], + [SecureClockGenerateTimeStamp, GenerateTimeStampRequest], + [GetRootOfTrustChallenge, GetRootOfTrustChallengeRequest], + [GetRootOfTrust, GetRootOfTrustRequest], + [SendRootOfTrust, SendRootOfTrustRequest], + [SetHalInfo, SetHalInfoRequest], + [SetBootInfo, SetBootInfoRequest], + [SetAttestationIds, SetAttestationIdsRequest], + [SetHalVersion, SetHalVersionRequest], + [SetAdditionalAttestationInfo, SetAdditionalAttestationInfoRequest], +) +PerformOpRsp = &( + [DeviceGetHardwareInfo, GetHardwareInfoResponse], + [DeviceAddRngEntropy, AddRngEntropyResponse], + [DeviceGenerateKey, GenerateKeyResponse], + [DeviceImportKey, ImportKeyResponse], + [DeviceImportWrappedKey, ImportWrappedKeyResponse], + [DeviceUpgradeKey, UpgradeKeyResponse], + [DeviceDeleteKey, DeleteKeyResponse], + [DeviceDeleteAllKeys, DeleteAllKeysResponse], + [DeviceDestroyAttestationIds, DestroyAttestationIdsResponse], + [DeviceBegin, BeginResponse], + [DeviceEarlyBootEnded, EarlyBootEndedResponse], + [DeviceConvertStorageKeyToEphemeral, ConvertStorageKeyToEphemeralResponse], + [DeviceGetKeyCharacteristics, GetKeyCharacteristicsResponse], + [OperationUpdateAad, UpdateAadResponse], + [OperationUpdate, UpdateResponse], + [OperationFinish, FinishResponse], + [OperationAbort, AbortResponse], + [RpcGetHardwareInfo, GetRpcHardwareInfoResponse], + [RpcGenerateEcdsaP256KeyPair, GenerateEcdsaP256KeyPairResponse], + [RpcGenerateCertificateRequest, GenerateCertificateRequestResponse], + [RpcGenerateCertificateV2Request, GenerateCertificateRequestV2Response], + [SharedSecretGetSharedSecretParameters, GetSharedSecretParametersResponse], + [SharedSecretComputeSharedSecret, ComputeSharedSecretResponse], + [SecureClockGenerateTimeStamp, GenerateTimeStampResponse], + [GetRootOfTrustChallenge, GetRootOfTrustChallengeResponse], + [GetRootOfTrust, GetRootOfTrustResponse], + [SendRootOfTrust, SendRootOfTrustResponse], + [SetHalInfo, SetHalInfoResponse], + [SetBootInfo, SetBootInfoResponse], + [SetAttestationIds, SetAttestationIdsResponse], + [SetHalVersion, SetHalVersionResponse], + [SetAdditionalAttestationInfo, SetAdditionalAttestationInfoResponse], +) +PerformOpResponse = [ + error_code: int, + rsp: [? PerformOpRsp], +] diff --git a/libs/rust/common/src/bin/cddl-dump.rs b/libs/rust/common/src/bin/cddl-dump.rs new file mode 100644 index 0000000..57ad755 --- /dev/null +++ b/libs/rust/common/src/bin/cddl-dump.rs @@ -0,0 +1,136 @@ +// Copyright 2022, 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. + +//! Utility to emit the CDDL for messages passed between HAL and TA. + +use kmr_common::crypto; +use kmr_wire::*; +use kmr_wire::{keymint::*, secureclock::*, sharedsecret::*}; + +fn show_schema() { + if let (Some(n), Some(s)) = (::cddl_typename(), ::cddl_schema()) { + println!("{} = {}", n, s); + } +} + +fn main() { + // CDDL corresponding to types defined by the AIDL spec. + + // newtype wrappers + show_schema::(); + show_schema::(); + show_schema::(); + + // enums + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + + // structs + show_schema::(); + // BeginResult omitted as it holds a Binder reference + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + + // Internal exhaustive enum (instead of `KeyParameter` and `KeyParameterValue` from the HAL). + show_schema::(); + + // CDDL corresponding to types defined in this crate. + + // enums + show_schema::(); + + // structs + + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); // Special case + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + + show_schema::(); + show_schema::(); + show_schema::(); + show_schema::(); + + show_schema::(); + show_schema::(); + + // Autogenerated enums + show_schema::(); + show_schema::(); + + // Overall response structure + show_schema::(); +} diff --git a/libs/rust/common/src/bin/keyblob-cddl-dump.rs b/libs/rust/common/src/bin/keyblob-cddl-dump.rs new file mode 100644 index 0000000..512b16f --- /dev/null +++ b/libs/rust/common/src/bin/keyblob-cddl-dump.rs @@ -0,0 +1,161 @@ +// Copyright 2022, 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. + +use kmr_common::{crypto, keyblob}; +use kmr_wire::keymint; +use std::collections::HashMap; +use std::fmt::Write; + +/// Combined schema, with CBOR-encoded examples of specific types. +#[derive(Default)] +struct AccumulatedSchema { + schema: String, + samples: HashMap>, +} + +impl AccumulatedSchema { + /// Add a new type to the accumulated schema, along with a sample instance of the type. + fn add(&mut self, sample: T) { + if let (Some(name), Some(schema)) = (::cddl_typename(), ::cddl_schema()) { + self.add_name_schema(&name, &schema); + self.samples.insert(name, sample.into_vec().unwrap()); + } else { + eprintln!("No CDDL typename+schema for {}", std::any::type_name::()); + } + } + + /// Add the given name = schema to the accumulated schema. + fn add_name_schema(&mut self, name: &str, schema: &str) { + let _ = writeln!(self.schema, "{} = {}", name, schema); + } + + /// Check that all of the sample type instances match their CDDL schema. + /// + /// This method is a no-op if the `cddl-cat` feature is not enabled. + fn check(&self) { + // TODO: enable this if/when cddl-cat supports tagged CBOR items (which are used in the + // EncryptedKeyBlob encoding) + #[cfg(feature = "cddl-cat")] + for (name, data) in &self.samples { + if let Err(e) = cddl_cat::validate_cbor_bytes(&name, &self.schema, &data) { + eprintln!("Failed to validate sample data for {} against CDDL: {:?}", name, e); + } + } + } +} + +impl std::fmt::Display for AccumulatedSchema { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.schema) + } +} + +fn main() { + // CDDL for encrypted keyblobs, top-down. + let mut schema = AccumulatedSchema::default(); + + schema.add(keyblob::EncryptedKeyBlob::V1(keyblob::EncryptedKeyBlobV1 { + characteristics: vec![], + key_derivation_input: [0u8; 32], + kek_context: vec![], + encrypted_key_material: coset::CoseEncrypt0Builder::new() + .protected( + coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build(), + ) + .ciphertext(vec![1, 2, 3]) + .build(), + secure_deletion_slot: Some(keyblob::SecureDeletionSlot(1)), + })); + schema.add(keyblob::Version::V1); + schema.add(keyblob::EncryptedKeyBlobV1 { + characteristics: vec![], + key_derivation_input: [0u8; 32], + kek_context: vec![], + encrypted_key_material: coset::CoseEncrypt0Builder::new() + .protected( + coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build(), + ) + .ciphertext(vec![1, 2, 3]) + .build(), + secure_deletion_slot: Some(keyblob::SecureDeletionSlot(1)), + }); + schema.add(keymint::KeyCharacteristics { + security_level: keymint::SecurityLevel::TrustedEnvironment, + authorizations: vec![], + }); + // From RFC 8152. + schema.add_name_schema( + "Cose_Encrypt0", + "[ protected: bstr, unprotected: { * (int / tstr) => any }, ciphertext: bstr / nil ]", + ); + + schema.add(crypto::KeyMaterial::Aes(crypto::aes::Key::Aes128([0u8; 16]).into())); + schema.add(keyblob::SecureDeletionSlot(1)); + schema.add(keyblob::SecureDeletionData { + factory_reset_secret: [0; 32], + secure_deletion_secret: [0; 16], + }); + schema.add(keyblob::RootOfTrustInfo { + verified_boot_key: vec![0; 32], + device_boot_locked: false, + verified_boot_state: keymint::VerifiedBootState::Unverified, + }); + schema.add(keymint::VerifiedBootState::Unverified); + + schema.add(keymint::SecurityLevel::TrustedEnvironment); + schema.add(keymint::KeyParam::CreationDatetime(keymint::DateTime { + ms_since_epoch: 22_593_600_000, + })); + schema.add(keymint::Tag::NoAuthRequired); + + schema.add(keymint::Algorithm::Ec); + schema.add(keymint::BlockMode::Ecb); + schema.add(keymint::Digest::None); + schema.add(keymint::EcCurve::Curve25519); + schema.add(crypto::CurveType::Nist); + schema.add(keymint::KeyOrigin::Generated); + schema.add(keymint::KeyPurpose::Sign); + schema.add(keymint::HardwareAuthenticatorType::Fingerprint); + schema.add(keymint::PaddingMode::None); + + schema.add(keymint::DateTime { ms_since_epoch: 22_593_600_000 }); + schema.add(kmr_wire::KeySizeInBits(256)); + schema.add(kmr_wire::RsaExponent(65537)); + + println!( + "; encrypted_key_material is AES-GCM encrypted with:\n\ + ; - key derived as described below\n\ + ; - plaintext is the CBOR-serialization of `KeyMaterial`\n\ + ; - nonce value is fixed, all zeroes\n\ + ; - no additional data\n\ + ;\n\ + ; Key derivation uses HKDF (RFC 5869) with HMAC-SHA256 to generate an AES-256 key:\n\ + ; - input keying material = a root key held in hardware\n\ + ; - salt = absent\n\ + ; - info = the following three or four chunks of context data concatenated:\n\ + ; - content of `EncryptedKeyBlob.key_derivation_input` (a random nonce)\n\ + ; - CBOR-serialization of `EncryptedKeyBlob.characteristics`\n\ + ; - CBOR-serialized array of additional hidden `KeyParam` items associated with the key, specifically:\n\ + ; - [Tag_ApplicationId, bstr] if required\n\ + ; - [Tag_ApplicationData, bstr] if required\n\ + ; - [Tag_RootOfTrust, bstr .cbor RootOfTrustInfo]\n\ + ; - (if secure storage is available) CBOR serialization of the `SecureDeletionData` structure, with:\n\ + ; - `factory_reset_secret` always populated\n\ + ; - `secure_deletion_secret` populated with:\n\ + ; - all zeroes (if `EncryptedKeyBlob.secure_deletion_slot` is empty)\n\ + ; - the contents of the slot (if `EncryptedKeyBlob.secure_deletion_slot` is non-empty)", + ); + println!("{}", schema); + schema.check(); +} diff --git a/libs/rust/common/src/crypto.rs b/libs/rust/common/src/crypto.rs new file mode 100644 index 0000000..62e40f0 --- /dev/null +++ b/libs/rust/common/src/crypto.rs @@ -0,0 +1,571 @@ +// Copyright 2022, 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. + +//! Abstractions and related types for accessing cryptographic primitives +//! and related functionality. + +// derive(N) generates a method that is missing a docstring. +#![allow(missing_docs)] + +use crate::{km_err, vec_try, vec_try_with_capacity, Error, FallibleAllocExt}; +use core::convert::{From, TryInto}; +use enumn::N; +use kmr_derive::AsCborValue; +use kmr_wire::keymint::{Algorithm, Digest, EcCurve}; +use kmr_wire::{cbor, cbor_type_error, AsCborValue, CborError, KeySizeInBits, RsaExponent}; +use log::error; +use spki::SubjectPublicKeyInfoRef; +use zeroize::ZeroizeOnDrop; + +pub mod aes; +pub mod des; +pub mod ec; +pub mod hmac; +pub mod rsa; +mod traits; +pub use traits::*; + +/// Size of SHA-256 output in bytes. +pub const SHA256_DIGEST_LEN: usize = 32; + +/// Function that mimics `slice.to_vec()` but which detects allocation failures. This version emits +/// `CborError` (instead of the `Error` that `crate::try_to_vec` emits). +#[inline] +pub fn try_to_vec(s: &[T]) -> Result, CborError> { + let mut v = vec_try_with_capacity!(s.len()).map_err(|_e| CborError::AllocationFailed)?; + v.extend_from_slice(s); + Ok(v) +} + +/// Milliseconds since an arbitrary epoch. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MillisecondsSinceEpoch(pub i64); + +impl From for kmr_wire::secureclock::Timestamp { + fn from(value: MillisecondsSinceEpoch) -> Self { + kmr_wire::secureclock::Timestamp { milliseconds: value.0 } + } +} + +/// Information for key generation. +#[derive(Clone)] +pub enum KeyGenInfo { + /// Generate an AES key of the given size. + Aes(aes::Variant), + /// Generate a 3-DES key. + TripleDes, + /// Generate an HMAC key of the given size. + Hmac(KeySizeInBits), + /// Generate an RSA keypair of the given size using the given exponent. + Rsa(KeySizeInBits, RsaExponent), + /// Generate a NIST EC keypair using the given curve. + NistEc(ec::NistCurve), + /// Generate an Ed25519 keypair. + Ed25519, + /// Generate an X25519 keypair. + X25519, +} + +/// Type of elliptic curve. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum CurveType { + /// NIST curve. + Nist = 0, + /// EdDSA curve. + EdDsa = 1, + /// XDH curve. + Xdh = 2, +} + +/// Raw key material used for deriving other keys. +#[derive(PartialEq, Eq, ZeroizeOnDrop)] +pub struct RawKeyMaterial(pub Vec); + +/// Opaque key material whose structure is only known/accessible to the crypto implementation. +/// The contents of this are assumed to be encrypted (and so are not `ZeroizeOnDrop`). +#[derive(Clone, PartialEq, Eq)] +pub struct OpaqueKeyMaterial(pub Vec); + +/// Wrapper that holds either a key of explicit type `T`, or an opaque blob of key material. +#[derive(Clone, PartialEq, Eq)] +pub enum OpaqueOr { + /// Explicit key material of the given type, available in plaintext. + Explicit(T), + /// Opaque key material, either encrypted or an opaque key handle. + Opaque(OpaqueKeyMaterial), +} + +/// Macro to provide `impl From for OpaqueOr`, so that explicit key material +/// automatically converts into the equivalent `OpaqueOr` variant. +macro_rules! opaque_from_key { + { $t:ty } => { + impl From<$t> for OpaqueOr<$t> { + fn from(k: $t) -> Self { + Self::Explicit(k) + } + } + } +} + +opaque_from_key!(aes::Key); +opaque_from_key!(des::Key); +opaque_from_key!(hmac::Key); +opaque_from_key!(rsa::Key); +opaque_from_key!(ec::Key); + +impl From for OpaqueOr { + fn from(k: OpaqueKeyMaterial) -> Self { + Self::Opaque(k) + } +} + +/// Key material that is held in plaintext (or is alternatively an opaque blob that is only +/// known/accessible to the crypto implementation, indicated by the `OpaqueOr::Opaque` variant). +#[derive(Clone, PartialEq, Eq)] +pub enum KeyMaterial { + /// AES symmetric key. + Aes(OpaqueOr), + /// 3-DES symmetric key. + TripleDes(OpaqueOr), + /// HMAC symmetric key. + Hmac(OpaqueOr), + /// RSA asymmetric key. + Rsa(OpaqueOr), + /// Elliptic curve asymmetric key. + Ec(EcCurve, CurveType, OpaqueOr), +} + +/// Macro that extracts the explicit key from an [`OpaqueOr`] wrapper. +#[macro_export] +macro_rules! explicit { + { $key:expr } => { + if let $crate::crypto::OpaqueOr::Explicit(k) = $key { + Ok(k) + } else { + Err($crate::km_err!(IncompatibleKeyFormat, "Expected explicit key but found opaque key!")) + } + } +} + +impl KeyMaterial { + /// Indicate whether the key material is for an asymmetric key. + pub fn is_asymmetric(&self) -> bool { + match self { + Self::Aes(_) | Self::TripleDes(_) | Self::Hmac(_) => false, + Self::Ec(_, _, _) | Self::Rsa(_) => true, + } + } + + /// Indicate whether the key material is for a symmetric key. + pub fn is_symmetric(&self) -> bool { + !self.is_asymmetric() + } + + /// Return the public key information as an ASN.1 DER encodable `SubjectPublicKeyInfo`, as + /// described in RFC 5280 section 4.1. + /// + /// ```asn1 + /// SubjectPublicKeyInfo ::= SEQUENCE { + /// algorithm AlgorithmIdentifier, + /// subjectPublicKey BIT STRING } + /// + /// AlgorithmIdentifier ::= SEQUENCE { + /// algorithm OBJECT IDENTIFIER, + /// parameters ANY DEFINED BY algorithm OPTIONAL } + /// ``` + /// + /// Returns `None` for a symmetric key. + pub fn subject_public_key_info<'a>( + &'a self, + buf: &'a mut Vec, + ec: &dyn Ec, + rsa: &dyn Rsa, + ) -> Result>, Error> { + Ok(match self { + Self::Rsa(key) => Some(key.subject_public_key_info(buf, rsa)?), + Self::Ec(curve, curve_type, key) => { + Some(key.subject_public_key_info(buf, ec, curve, curve_type)?) + } + _ => None, + }) + } +} + +/// Manual implementation of [`Debug`] that skips emitting plaintext key material. +impl core::fmt::Debug for KeyMaterial { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Aes(k) => match k { + OpaqueOr::Explicit(aes::Key::Aes128(_)) => f.write_str("Aes128(...)"), + OpaqueOr::Explicit(aes::Key::Aes192(_)) => f.write_str("Aes192(...)"), + OpaqueOr::Explicit(aes::Key::Aes256(_)) => f.write_str("Aes256(...)"), + OpaqueOr::Opaque(_) => f.write_str("Aes(opaque)"), + }, + Self::TripleDes(_) => f.write_str("TripleDes(...)"), + Self::Hmac(_) => f.write_str("Hmac(...)"), + Self::Rsa(_) => f.write_str("Rsa(...)"), + Self::Ec(c, _, _) => f.write_fmt(format_args!("Ec({:?}, ...)", c)), + } + } +} + +impl AsCborValue for KeyMaterial { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let mut a = match value { + cbor::value::Value::Array(a) if a.len() == 3 => a, + _ => return cbor_type_error(&value, "arr len 3"), + }; + let raw_key_value = a.remove(2); + let opaque = match a.remove(1) { + cbor::value::Value::Bool(b) => b, + v => return cbor_type_error(&v, "bool"), + }; + let algo: i32 = match a.remove(0) { + cbor::value::Value::Integer(i) => i.try_into()?, + v => return cbor_type_error(&v, "uint"), + }; + + match algo { + x if x == Algorithm::Aes as i32 => { + let raw_key = >::from_cbor_value(raw_key_value)?; + if opaque { + Ok(Self::Aes(OpaqueKeyMaterial(raw_key).into())) + } else { + match aes::Key::new(raw_key) { + Ok(k) => Ok(Self::Aes(k.into())), + Err(_e) => Err(CborError::UnexpectedItem("bstr", "bstr len 16/24/32")), + } + } + } + x if x == Algorithm::TripleDes as i32 => { + let raw_key = >::from_cbor_value(raw_key_value)?; + if opaque { + Ok(Self::TripleDes(OpaqueKeyMaterial(raw_key).into())) + } else { + Ok(Self::TripleDes( + des::Key( + raw_key + .try_into() + .map_err(|_e| CborError::UnexpectedItem("bstr", "bstr len 24"))?, + ) + .into(), + )) + } + } + x if x == Algorithm::Hmac as i32 => { + let raw_key = >::from_cbor_value(raw_key_value)?; + if opaque { + Ok(Self::Hmac(OpaqueKeyMaterial(raw_key).into())) + } else { + Ok(Self::Hmac(hmac::Key(raw_key).into())) + } + } + x if x == Algorithm::Rsa as i32 => { + let raw_key = >::from_cbor_value(raw_key_value)?; + if opaque { + Ok(Self::Rsa(OpaqueKeyMaterial(raw_key).into())) + } else { + Ok(Self::Rsa(rsa::Key(raw_key).into())) + } + } + x if x == Algorithm::Ec as i32 => { + let mut a = match raw_key_value { + cbor::value::Value::Array(a) if a.len() == 3 => a, + _ => return cbor_type_error(&raw_key_value, "arr len 2"), + }; + let raw_key_value = a.remove(2); + let raw_key = >::from_cbor_value(raw_key_value)?; + let curve_type = CurveType::from_cbor_value(a.remove(1))?; + let curve = ::from_cbor_value(a.remove(0))?; + if opaque { + Ok(Self::Ec(curve, curve_type, OpaqueKeyMaterial(raw_key).into())) + } else { + let key = match (curve, curve_type) { + (EcCurve::P224, CurveType::Nist) => ec::Key::P224(ec::NistKey(raw_key)), + (EcCurve::P256, CurveType::Nist) => ec::Key::P256(ec::NistKey(raw_key)), + (EcCurve::P384, CurveType::Nist) => ec::Key::P384(ec::NistKey(raw_key)), + (EcCurve::P521, CurveType::Nist) => ec::Key::P521(ec::NistKey(raw_key)), + (EcCurve::Curve25519, CurveType::EdDsa) => { + let key = raw_key.try_into().map_err(|_e| { + error!("decoding Ed25519 key of incorrect len"); + CborError::OutOfRangeIntegerValue + })?; + ec::Key::Ed25519(ec::Ed25519Key(key)) + } + (EcCurve::Curve25519, CurveType::Xdh) => { + let key = raw_key.try_into().map_err(|_e| { + error!("decoding X25519 key of incorrect len"); + CborError::OutOfRangeIntegerValue + })?; + ec::Key::X25519(ec::X25519Key(key)) + } + (_, _) => { + error!("Unexpected EC combination ({:?}, {:?})", curve, curve_type); + return Err(CborError::NonEnumValue); + } + }; + Ok(Self::Ec(curve, curve_type, key.into())) + } + } + _ => Err(CborError::UnexpectedItem("unknown enum", "algo enum")), + } + } + + fn to_cbor_value(self) -> Result { + let cbor_alloc_err = |_e| CborError::AllocationFailed; + Ok(cbor::value::Value::Array(match self { + Self::Aes(OpaqueOr::Opaque(OpaqueKeyMaterial(k))) => vec_try![ + cbor::value::Value::Integer((Algorithm::Aes as i32).into()), + cbor::value::Value::Bool(true), + cbor::value::Value::Bytes(try_to_vec(&k)?), + ] + .map_err(cbor_alloc_err)?, + Self::TripleDes(OpaqueOr::Opaque(OpaqueKeyMaterial(k))) => vec_try![ + cbor::value::Value::Integer((Algorithm::TripleDes as i32).into()), + cbor::value::Value::Bool(true), + cbor::value::Value::Bytes(try_to_vec(&k)?), + ] + .map_err(cbor_alloc_err)?, + Self::Hmac(OpaqueOr::Opaque(OpaqueKeyMaterial(k))) => vec_try![ + cbor::value::Value::Integer((Algorithm::Hmac as i32).into()), + cbor::value::Value::Bool(true), + cbor::value::Value::Bytes(try_to_vec(&k)?), + ] + .map_err(cbor_alloc_err)?, + Self::Rsa(OpaqueOr::Opaque(OpaqueKeyMaterial(k))) => vec_try![ + cbor::value::Value::Integer((Algorithm::Rsa as i32).into()), + cbor::value::Value::Bool(true), + cbor::value::Value::Bytes(try_to_vec(&k)?), + ] + .map_err(cbor_alloc_err)?, + Self::Ec(curve, curve_type, OpaqueOr::Opaque(OpaqueKeyMaterial(k))) => vec_try![ + cbor::value::Value::Integer((Algorithm::Ec as i32).into()), + cbor::value::Value::Bool(true), + cbor::value::Value::Array( + vec_try![ + cbor::value::Value::Integer((curve as i32).into()), + cbor::value::Value::Integer((curve_type as i32).into()), + cbor::value::Value::Bytes(try_to_vec(&k)?), + ] + .map_err(cbor_alloc_err)? + ), + ] + .map_err(cbor_alloc_err)?, + + Self::Aes(OpaqueOr::Explicit(k)) => vec_try![ + cbor::value::Value::Integer((Algorithm::Aes as i32).into()), + cbor::value::Value::Bool(false), + match k { + aes::Key::Aes128(k) => cbor::value::Value::Bytes(try_to_vec(&k)?), + aes::Key::Aes192(k) => cbor::value::Value::Bytes(try_to_vec(&k)?), + aes::Key::Aes256(k) => cbor::value::Value::Bytes(try_to_vec(&k)?), + }, + ] + .map_err(cbor_alloc_err)?, + + Self::TripleDes(OpaqueOr::Explicit(k)) => vec_try![ + cbor::value::Value::Integer((Algorithm::TripleDes as i32).into()), + cbor::value::Value::Bool(false), + cbor::value::Value::Bytes(k.0.to_vec()), + ] + .map_err(cbor_alloc_err)?, + Self::Hmac(OpaqueOr::Explicit(k)) => vec_try![ + cbor::value::Value::Integer((Algorithm::Hmac as i32).into()), + cbor::value::Value::Bool(false), + cbor::value::Value::Bytes(k.0.clone()), + ] + .map_err(cbor_alloc_err)?, + Self::Rsa(OpaqueOr::Explicit(k)) => vec_try![ + cbor::value::Value::Integer((Algorithm::Rsa as i32).into()), + cbor::value::Value::Bool(false), + cbor::value::Value::Bytes(k.0.clone()), + ] + .map_err(cbor_alloc_err)?, + Self::Ec(curve, curve_type, OpaqueOr::Explicit(k)) => vec_try![ + cbor::value::Value::Integer((Algorithm::Ec as i32).into()), + cbor::value::Value::Bool(false), + cbor::value::Value::Array( + vec_try![ + cbor::value::Value::Integer((curve as i32).into()), + cbor::value::Value::Integer((curve_type as i32).into()), + cbor::value::Value::Bytes(k.private_key_bytes().to_vec()), + ] + .map_err(cbor_alloc_err)?, + ), + ] + .map_err(cbor_alloc_err)?, + })) + } + + fn cddl_typename() -> Option { + Some("KeyMaterial".to_string()) + } + + fn cddl_schema() -> Option { + Some(format!( + "&( + ; For each variant the `bool` second entry indicates whether the bstr for the key material + ; is opaque (true), or explicit (false). + [{}, bool, bstr], ; {} + [{}, bool, bstr], ; {} + [{}, bool, bstr], ; {} + ; An explicit RSA key is in the form of an ASN.1 DER encoding of a PKCS#1 `RSAPrivateKey` + ; structure, as specified by RFC 3447 sections A.1.2 and 3.2. + [{}, bool, bstr], ; {} + ; An explicit EC key for a NIST curve is in the form of an ASN.1 DER encoding of a + ; `ECPrivateKey` structure, as specified by RFC 5915 section 3. + ; An explicit EC key for curve 25519 is the raw key bytes. + [{}, bool, [EcCurve, CurveType, bstr]], ; {} +)", + Algorithm::Aes as i32, + "Algorithm_Aes", + Algorithm::TripleDes as i32, + "Algorithm_TripleDes", + Algorithm::Hmac as i32, + "Algorithm_Hmac", + Algorithm::Rsa as i32, + "Algorithm_Rsa", + Algorithm::Ec as i32, + "Algorithm_Ec", + )) + } +} + +/// Direction of cipher operation. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SymmetricOperation { + /// Perform encryption. + Encrypt, + /// Perform decryption. + Decrypt, +} + +/// Extract or generate a nonce of the given size. +pub fn nonce( + size: usize, + caller_nonce: Option<&Vec>, + rng: &mut dyn Rng, +) -> Result, Error> { + match caller_nonce { + Some(n) => match n.len() { + l if l == size => Ok(n.clone()), + _ => Err(km_err!(InvalidNonce, "want {} byte nonce", size)), + }, + None => { + let mut n = vec_try![0; size]?; + rng.fill_bytes(&mut n); + Ok(n) + } + } +} + +/// Salt value used in HKDF if none provided. +const HKDF_EMPTY_SALT: [u8; SHA256_DIGEST_LEN] = [0; SHA256_DIGEST_LEN]; + +/// Convenience wrapper to perform one-shot HMAC-SHA256. +pub fn hmac_sha256(hmac: &dyn Hmac, key: &[u8], data: &[u8]) -> Result, Error> { + let mut op = hmac.begin(hmac::Key(crate::try_to_vec(key)?).into(), Digest::Sha256)?; + op.update(data)?; + op.finish() +} + +/// Default implementation of [`Hkdf`] for any type implementing [`Hmac`]. +impl Hkdf for T { + fn extract(&self, mut salt: &[u8], ikm: &[u8]) -> Result, Error> { + if salt.is_empty() { + salt = &HKDF_EMPTY_SALT[..]; + } + let prk = hmac_sha256(self, salt, ikm)?; + Ok(OpaqueOr::Explicit(hmac::Key::new(prk))) + } + + fn expand( + &self, + prk: &OpaqueOr, + info: &[u8], + out_len: usize, + ) -> Result, Error> { + let prk = &explicit!(prk)?.0; + let n = out_len.div_ceil(SHA256_DIGEST_LEN); + if n > 256 { + return Err(km_err!(InvalidArgument, "overflow in hkdf")); + } + let mut t = vec_try_with_capacity!(SHA256_DIGEST_LEN)?; + let mut okm = vec_try_with_capacity!(n * SHA256_DIGEST_LEN)?; + let n = n as u8; + for idx in 0..n { + let mut input = vec_try_with_capacity!(t.len() + info.len() + 1)?; + input.extend_from_slice(&t); + input.extend_from_slice(info); + input.push(idx + 1); + + t = hmac_sha256(self, prk, &input)?; + okm.try_extend_from_slice(&t)?; + } + okm.truncate(out_len); + Ok(okm) + } +} + +/// Default implementation of [`Ckdf`] for any type implementing [`AesCmac`]. +impl Ckdf for T { + fn ckdf( + &self, + key: &OpaqueOr, + label: &[u8], + chunks: &[&[u8]], + out_len: usize, + ) -> Result, Error> { + let key = explicit!(key)?; + // Note: the variables i and l correspond to i and L in the standard. See page 12 of + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf. + + let blocks: u32 = out_len.div_ceil(aes::BLOCK_SIZE) as u32; + let l = (out_len * 8) as u32; // in bits + let net_order_l = l.to_be_bytes(); + let zero_byte: [u8; 1] = [0]; + let mut output = vec_try![0; out_len]?; + let mut output_pos = 0; + + for i in 1u32..=blocks { + // Data to mac is (i:u32 || label || 0x00:u8 || context || L:u32), with integers in + // network order. + let mut op = self.begin(key.clone().into())?; + let net_order_i = i.to_be_bytes(); + op.update(&net_order_i[..])?; + op.update(label)?; + op.update(&zero_byte[..])?; + for chunk in chunks { + op.update(chunk)?; + } + op.update(&net_order_l[..])?; + + let data = op.finish()?; + let copy_len = core::cmp::min(data.len(), output.len() - output_pos); + output[output_pos..output_pos + copy_len].clone_from_slice(&data[..copy_len]); + output_pos += copy_len; + } + if output_pos != output.len() { + return Err(km_err!( + InvalidArgument, + "finished at {} before end of output at {}", + output_pos, + output.len() + )); + } + Ok(output) + } +} diff --git a/libs/rust/common/src/crypto/aes.rs b/libs/rust/common/src/crypto/aes.rs new file mode 100644 index 0000000..1f22c4b --- /dev/null +++ b/libs/rust/common/src/crypto/aes.rs @@ -0,0 +1,238 @@ +// Copyright 2022, 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. + +//! Functionality related to AES encryption + +use super::{nonce, Rng}; +use crate::{get_tag_value, km_err, tag, try_to_vec, Error}; +use core::convert::TryInto; +use kmr_wire::keymint::{BlockMode, ErrorCode, KeyParam, PaddingMode}; +use kmr_wire::KeySizeInBits; +use zeroize::ZeroizeOnDrop; + +/// Size of an AES block in bytes. +pub const BLOCK_SIZE: usize = 16; + +/// Size of AES-GCM nonce in bytes. +pub const GCM_NONCE_SIZE: usize = 12; // 96 bits + +/// AES variant. +#[derive(Clone)] +pub enum Variant { + /// AES-128 + Aes128, + /// AES-192 + Aes192, + /// AES-256 + Aes256, +} + +impl Variant { + /// Size in bytes of the corresponding AES key. + pub fn key_size(&self) -> usize { + match self { + Self::Aes128 => 16, + Self::Aes192 => 24, + Self::Aes256 => 32, + } + } +} + +/// An AES-128, AES-192 or AES-256 key. +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub enum Key { + /// AES-128 + Aes128([u8; 16]), + /// AES-192 + Aes192([u8; 24]), + /// AES-256 + Aes256([u8; 32]), +} + +impl Key { + /// Create a new [`Key`] from raw data, which must be 16, 24 or 32 bytes long. + pub fn new(data: Vec) -> Result { + match data.len() { + 16 => Ok(Key::Aes128(data.try_into().unwrap())), // safe: len checked + 24 => Ok(Key::Aes192(data.try_into().unwrap())), // safe: len checked + 32 => Ok(Key::Aes256(data.try_into().unwrap())), // safe: len checked + l => Err(km_err!(UnsupportedKeySize, "AES keys must be 16, 24 or 32 bytes not {}", l)), + } + } + /// Create a new [`Key`] from raw data, which must be 16, 24 or 32 bytes long. + pub fn new_from(data: &[u8]) -> Result { + Key::new(try_to_vec(data)?) + } + + /// Indicate the size of the key in bits. + pub fn size(&self) -> KeySizeInBits { + KeySizeInBits(match self { + Key::Aes128(_) => 128, + Key::Aes192(_) => 192, + Key::Aes256(_) => 256, + }) + } +} + +/// Mode of AES plain cipher operation. Associated value is the nonce. +#[derive(Clone, Copy, Debug)] +pub enum CipherMode { + /// ECB mode with no padding. + EcbNoPadding, + /// ECB mode with PKCS#7 padding. + EcbPkcs7Padding, + /// CBC mode with no padding. + CbcNoPadding { + /// Nonce to use. + nonce: [u8; BLOCK_SIZE], + }, + /// CBC mode with PKCS#7 padding. + CbcPkcs7Padding { + /// Nonce to use. + nonce: [u8; BLOCK_SIZE], + }, + /// CTR mode with the given nonce. + Ctr { + /// Nonce to use. + nonce: [u8; BLOCK_SIZE], + }, +} + +/// Mode of AES-GCM operation. Associated value is the nonce, size of +/// tag is indicated by the variant name. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug)] +pub enum GcmMode { + GcmTag12 { nonce: [u8; GCM_NONCE_SIZE] }, + GcmTag13 { nonce: [u8; GCM_NONCE_SIZE] }, + GcmTag14 { nonce: [u8; GCM_NONCE_SIZE] }, + GcmTag15 { nonce: [u8; GCM_NONCE_SIZE] }, + GcmTag16 { nonce: [u8; GCM_NONCE_SIZE] }, +} + +/// Mode of AES operation. +#[derive(Clone, Copy, Debug)] +pub enum Mode { + /// Perform unauthenticated cipher operation. + Cipher(CipherMode), + /// Perform authenticated cipher with additional data operation. + Aead(GcmMode), +} + +impl Mode { + /// Determine the [`Mode`], rejecting invalid parameters. Use `caller_nonce` if provided, + /// otherwise generate a new nonce using the provided [`Rng`] instance. + pub fn new( + params: &[KeyParam], + caller_nonce: Option<&Vec>, + rng: &mut dyn Rng, + ) -> Result { + let mode = tag::get_block_mode(params)?; + let padding = tag::get_padding_mode(params)?; + match mode { + BlockMode::Ecb => { + if caller_nonce.is_some() { + return Err(km_err!(InvalidNonce, "nonce unexpectedly provided for AES-ECB")); + } + match padding { + PaddingMode::None => Ok(Mode::Cipher(CipherMode::EcbNoPadding)), + PaddingMode::Pkcs7 => Ok(Mode::Cipher(CipherMode::EcbPkcs7Padding)), + _ => Err(km_err!( + IncompatiblePaddingMode, + "expected NONE/PKCS7 padding for AES-ECB" + )), + } + } + BlockMode::Cbc => { + let nonce: [u8; BLOCK_SIZE] = + nonce(BLOCK_SIZE, caller_nonce, rng)?.try_into().map_err(|_e| { + km_err!(InvalidNonce, "want {} byte nonce for AES-CBC", BLOCK_SIZE) + })?; + match padding { + PaddingMode::None => Ok(Mode::Cipher(CipherMode::CbcNoPadding { nonce })), + PaddingMode::Pkcs7 => Ok(Mode::Cipher(CipherMode::CbcPkcs7Padding { nonce })), + _ => Err(km_err!( + IncompatiblePaddingMode, + "expected NONE/PKCS7 padding for AES-CBC" + )), + } + } + BlockMode::Ctr => { + if padding != PaddingMode::None { + return Err(km_err!( + IncompatiblePaddingMode, + "expected NONE padding for AES-CTR" + )); + } + let nonce: [u8; BLOCK_SIZE] = + nonce(BLOCK_SIZE, caller_nonce, rng)?.try_into().map_err(|_e| { + km_err!(InvalidNonce, "want {} byte nonce for AES-CTR", BLOCK_SIZE) + })?; + Ok(Mode::Cipher(CipherMode::Ctr { nonce })) + } + BlockMode::Gcm => { + if padding != PaddingMode::None { + return Err(km_err!( + IncompatiblePaddingMode, + "expected NONE padding for AES-GCM" + )); + } + let nonce: [u8; GCM_NONCE_SIZE] = nonce(GCM_NONCE_SIZE, caller_nonce, rng)? + .try_into() + .map_err(|_e| km_err!(InvalidNonce, "want 12 byte nonce for AES-GCM"))?; + let tag_len = get_tag_value!(params, MacLength, ErrorCode::InvalidMacLength)?; + if tag_len % 8 != 0 { + return Err(km_err!( + InvalidMacLength, + "tag length {} not a multiple of 8", + tag_len + )); + } + match tag_len / 8 { + 12 => Ok(Mode::Aead(GcmMode::GcmTag12 { nonce })), + 13 => Ok(Mode::Aead(GcmMode::GcmTag13 { nonce })), + 14 => Ok(Mode::Aead(GcmMode::GcmTag14 { nonce })), + 15 => Ok(Mode::Aead(GcmMode::GcmTag15 { nonce })), + 16 => Ok(Mode::Aead(GcmMode::GcmTag16 { nonce })), + v => Err(km_err!( + InvalidMacLength, + "want 12-16 byte tag for AES-GCM not {} bytes", + v + )), + } + } + } + } + + /// Indicate whether the AES mode is an AEAD. + pub fn is_aead(&self) -> bool { + match self { + Mode::Aead(_) => true, + Mode::Cipher(_) => false, + } + } +} + +impl GcmMode { + /// Return the tag length (in bytes) for an AES-GCM mode. + pub fn tag_len(&self) -> usize { + match self { + GcmMode::GcmTag12 { nonce: _ } => 12, + GcmMode::GcmTag13 { nonce: _ } => 13, + GcmMode::GcmTag14 { nonce: _ } => 14, + GcmMode::GcmTag15 { nonce: _ } => 15, + GcmMode::GcmTag16 { nonce: _ } => 16, + } + } +} diff --git a/libs/rust/common/src/crypto/des.rs b/libs/rust/common/src/crypto/des.rs new file mode 100644 index 0000000..e32cbd9 --- /dev/null +++ b/libs/rust/common/src/crypto/des.rs @@ -0,0 +1,117 @@ +// Copyright 2022, 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. + +//! Functionality related to triple DES encryption + +use super::{nonce, Rng}; +use crate::{km_err, tag, try_to_vec, Error}; +use core::convert::TryInto; +use kmr_wire::{ + keymint::{BlockMode, KeyParam, PaddingMode}, + KeySizeInBits, +}; +use zeroize::ZeroizeOnDrop; + +/// Size of an DES block in bytes. +pub const BLOCK_SIZE: usize = 8; + +/// The size of a 3-DES key in bits. +pub const KEY_SIZE_BITS: KeySizeInBits = KeySizeInBits(168); + +/// The size of a 3-DES key in bytes. Note that this is `KEY_SIZE_BITS` / 7, not +/// `KEY_SIZE_BITS` / 8 because each byte has a check bit (even though this check +/// bit is never actually checked). +pub const KEY_SIZE_BYTES: usize = 24; + +/// A 3-DES key. The key data is 24 bytes / 192 bits in length, but only 7/8 of the +/// bits are used giving an effective key size of 168 bits. +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct Key(pub [u8; KEY_SIZE_BYTES]); + +impl Key { + /// Create a new 3-DES key from 24 bytes of data. + pub fn new(data: Vec) -> Result { + Ok(Key(data + .try_into() + .map_err(|_e| km_err!(UnsupportedKeySize, "3-DES key size wrong"))?)) + } + /// Create a new 3-DES key from 24 bytes of data. + pub fn new_from(data: &[u8]) -> Result { + let data = try_to_vec(data)?; + Ok(Key(data + .try_into() + .map_err(|_e| km_err!(UnsupportedKeySize, "3-DES key size wrong"))?)) + } +} + +/// Mode of DES operation. Associated value is the nonce. +#[derive(Clone, Copy, Debug)] +pub enum Mode { + /// ECB mode with no padding. + EcbNoPadding, + /// ECB mode with PKCS#7 padding. + EcbPkcs7Padding, + /// CBC mode with no padding. + CbcNoPadding { + /// Nonce to use. + nonce: [u8; BLOCK_SIZE], + }, + /// CBC mode with PKCS#7 padding. + CbcPkcs7Padding { + /// Nonce to use. + nonce: [u8; BLOCK_SIZE], + }, +} + +impl Mode { + /// Determine the [`Mode`], rejecting invalid parameters. Use `caller_nonce` if provided, + /// otherwise generate a new nonce using the provided [`Rng`] instance. + pub fn new( + params: &[KeyParam], + caller_nonce: Option<&Vec>, + rng: &mut dyn Rng, + ) -> Result { + let mode = tag::get_block_mode(params)?; + let padding = tag::get_padding_mode(params)?; + match mode { + BlockMode::Ecb => { + if caller_nonce.is_some() { + return Err(km_err!(InvalidNonce, "nonce unexpectedly provided")); + } + match padding { + PaddingMode::None => Ok(Mode::EcbNoPadding), + PaddingMode::Pkcs7 => Ok(Mode::EcbPkcs7Padding), + _ => Err(km_err!( + IncompatiblePaddingMode, + "expected NONE/PKCS7 padding for DES-ECB" + )), + } + } + BlockMode::Cbc => { + let nonce: [u8; BLOCK_SIZE] = nonce(BLOCK_SIZE, caller_nonce, rng)? + .try_into() + .map_err(|_e| km_err!(InvalidNonce, "want {} byte nonce", BLOCK_SIZE))?; + match padding { + PaddingMode::None => Ok(Mode::CbcNoPadding { nonce }), + PaddingMode::Pkcs7 => Ok(Mode::CbcPkcs7Padding { nonce }), + _ => Err(km_err!( + IncompatiblePaddingMode, + "expected NONE/PKCS7 padding for DES-CBC" + )), + } + } + _ => Err(km_err!(UnsupportedBlockMode, "want ECB/CBC")), + } + } +} diff --git a/libs/rust/common/src/crypto/ec.rs b/libs/rust/common/src/crypto/ec.rs new file mode 100644 index 0000000..97b2998 --- /dev/null +++ b/libs/rust/common/src/crypto/ec.rs @@ -0,0 +1,673 @@ +// Copyright 2022, 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. + +//! Functionality related to elliptic curve support. + +use super::{CurveType, KeyMaterial, OpaqueOr}; +use crate::{der_err, km_err, try_to_vec, vec_try, Error, FallibleAllocExt}; +use der::{asn1::BitStringRef, AnyRef, Decode, Encode, Sequence}; +use kmr_wire::{coset, keymint::EcCurve, rpc, KeySizeInBits}; +use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo, SubjectPublicKeyInfoRef}; +use zeroize::ZeroizeOnDrop; + +/// Size (in bytes) of a curve 25519 private key. +pub const CURVE25519_PRIV_KEY_LEN: usize = 32; + +/// Maximum message size for Ed25519 Signing operations. +pub const MAX_ED25519_MSG_SIZE: usize = 16 * 1024; + +/// Marker value used to indicate that a public key is for RKP test mode. +pub const RKP_TEST_KEY_CBOR_MARKER: i64 = -70000; + +/// Initial byte of SEC1 public key encoding that indicates an uncompressed point. +pub const SEC1_UNCOMPRESSED_PREFIX: u8 = 0x04; + +/// OID value for general-use NIST EC keys held in PKCS#8 and X.509; see RFC 5480 s2.1.1. +pub const X509_NIST_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); + +/// OID value for Ed25519 keys held in PKCS#8 and X.509; see RFC 8410 s3. +pub const X509_ED25519_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.3.101.112"); + +/// OID value for X25519 keys held in PKCS#8 and X.509; see RFC 8410 s3. +pub const X509_X25519_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.3.101.110"); + +/// OID value for PKCS#1 signature with SHA-256 and ECDSA, see RFC 5758 s3.2. +pub const ECDSA_SHA256_SIGNATURE_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2"); + +/// OID value in `AlgorithmIdentifier.parameters` for P-224; see RFC 5480 s2.1.1.1. +pub const ALGO_PARAM_P224_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.33"); + +/// OID value in `AlgorithmIdentifier.parameters` for P-256; see RFC 5480 s2.1.1.1. +pub const ALGO_PARAM_P256_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); + +/// OID value in `AlgorithmIdentifier.parameters` for P-384; see RFC 5480 s2.1.1.1. +pub const ALGO_PARAM_P384_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.34"); + +/// OID value in `AlgorithmIdentifier.parameters` for P-521; see RFC 5480 s2.1.1.1. +pub const ALGO_PARAM_P521_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.35"); + +/// Subset of `EcCurve` values that are NIST curves. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[repr(i32)] +pub enum NistCurve { + /// P-224 + P224 = 0, + /// P-256 + P256 = 1, + /// P-384 + P384 = 2, + /// P-521 + P521 = 3, +} + +impl NistCurve { + /// Curve coordinate size in bytes. + pub fn coord_len(&self) -> usize { + match self { + NistCurve::P224 => 28, + NistCurve::P256 => 32, + NistCurve::P384 => 48, + NistCurve::P521 => 66, + } + } +} + +impl From for EcCurve { + fn from(nist: NistCurve) -> EcCurve { + match nist { + NistCurve::P224 => EcCurve::P224, + NistCurve::P256 => EcCurve::P256, + NistCurve::P384 => EcCurve::P384, + NistCurve::P521 => EcCurve::P521, + } + } +} + +impl TryFrom for NistCurve { + type Error = Error; + fn try_from(curve: EcCurve) -> Result { + match curve { + EcCurve::P224 => Ok(NistCurve::P224), + EcCurve::P256 => Ok(NistCurve::P256), + EcCurve::P384 => Ok(NistCurve::P384), + EcCurve::P521 => Ok(NistCurve::P521), + EcCurve::Curve25519 => Err(km_err!(InvalidArgument, "curve 25519 is not a NIST curve")), + } + } +} + +impl OpaqueOr { + /// Encode into `buf` the public key information as an ASN.1 DER encodable + /// `SubjectPublicKeyInfo`, as described in RFC 5280 section 4.1. + /// + /// ```asn1 + /// SubjectPublicKeyInfo ::= SEQUENCE { + /// algorithm AlgorithmIdentifier, + /// subjectPublicKey BIT STRING } + /// + /// AlgorithmIdentifier ::= SEQUENCE { + /// algorithm OBJECT IDENTIFIER, + /// parameters ANY DEFINED BY algorithm OPTIONAL } + /// ``` + /// + /// For NIST curve EC keys, the contents are described in RFC 5480 section 2.1. + /// - The `AlgorithmIdentifier` has an `algorithm` OID of 1.2.840.10045.2.1. + /// - The `AlgorithmIdentifier` has `parameters` that hold an OID identifying the curve, here + /// one of: + /// - P-224: 1.3.132.0.33 + /// - P-256: 1.2.840.10045.3.1.7 + /// - P-384: 1.3.132.0.34 + /// - P-521: 1.3.132.0.35 + /// - The `subjectPublicKey` bit string holds an ASN.1 DER-encoded `OCTET STRING` that contains + /// a SEC-1 encoded public key. The first byte indicates the format: + /// - 0x04: uncompressed, followed by x || y coordinates + /// - 0x03: compressed, followed by x coordinate (and with a odd y coordinate) + /// - 0x02: compressed, followed by x coordinate (and with a even y coordinate) + /// + /// For Ed25519 keys, the contents of the `AlgorithmIdentifier` are described in RFC 8410 + /// section 3. + /// - The `algorithm` has an OID of 1.3.101.112. + /// - The `parameters` are absent. + /// + /// The `subjectPublicKey` holds the raw key bytes. + /// + /// For X25519 keys, the contents of the `AlgorithmIdentifier` are described in RFC 8410 + /// section 3. + /// - The `algorithm` has an OID of 1.3.101.110. + /// - The `parameters` are absent. + /// + /// The `subjectPublicKey` holds the raw key bytes. + pub fn subject_public_key_info<'a>( + &'a self, + buf: &'a mut Vec, + ec: &dyn super::Ec, + curve: &EcCurve, + curve_type: &CurveType, + ) -> Result, Error> { + buf.try_extend_from_slice(&ec.subject_public_key(self)?)?; + let (oid, parameters) = match curve_type { + CurveType::Nist => { + let nist_curve: NistCurve = (*curve).try_into()?; + let params_oid = match nist_curve { + NistCurve::P224 => &ALGO_PARAM_P224_OID, + NistCurve::P256 => &ALGO_PARAM_P256_OID, + NistCurve::P384 => &ALGO_PARAM_P384_OID, + NistCurve::P521 => &ALGO_PARAM_P521_OID, + }; + (X509_NIST_OID, Some(AnyRef::from(params_oid))) + } + CurveType::EdDsa => (X509_ED25519_OID, None), + CurveType::Xdh => (X509_X25519_OID, None), + }; + Ok(SubjectPublicKeyInfo { + algorithm: AlgorithmIdentifier { oid, parameters }, + subject_public_key: BitStringRef::from_bytes(buf).unwrap(), + }) + } + + /// Generate a `COSE_Key` for the public key. + pub fn public_cose_key( + &self, + ec: &dyn super::Ec, + curve: EcCurve, + curve_type: CurveType, + purpose: CoseKeyPurpose, + key_id: Option>, + test_mode: rpc::TestMode, + ) -> Result { + let nist_algo = match purpose { + CoseKeyPurpose::Agree => coset::iana::Algorithm::ECDH_ES_HKDF_256, + CoseKeyPurpose::Sign => coset::iana::Algorithm::ES256, + }; + + let pub_key = ec.subject_public_key(self)?; + let mut builder = match curve_type { + CurveType::Nist => { + let nist_curve: NistCurve = curve.try_into()?; + let (x, y) = coordinates_from_pub_key(pub_key, nist_curve)?; + let cose_nist_curve = match nist_curve { + NistCurve::P224 => { + // P-224 is not supported by COSE: there is no value in the COSE Elliptic + // Curve registry for it. + return Err(km_err!(Unimplemented, "no COSE support for P-224")); + } + NistCurve::P256 => coset::iana::EllipticCurve::P_256, + NistCurve::P384 => coset::iana::EllipticCurve::P_384, + NistCurve::P521 => coset::iana::EllipticCurve::P_521, + }; + coset::CoseKeyBuilder::new_ec2_pub_key(cose_nist_curve, x, y).algorithm(nist_algo) + } + CurveType::EdDsa => coset::CoseKeyBuilder::new_okp_key() + .param( + coset::iana::OkpKeyParameter::Crv as i64, + coset::cbor::value::Value::from(coset::iana::EllipticCurve::Ed25519 as u64), + ) + .param( + coset::iana::OkpKeyParameter::X as i64, + coset::cbor::value::Value::from(pub_key), + ) + .algorithm(coset::iana::Algorithm::EdDSA), + CurveType::Xdh => coset::CoseKeyBuilder::new_okp_key() + .param( + coset::iana::OkpKeyParameter::Crv as i64, + coset::cbor::value::Value::from(coset::iana::EllipticCurve::X25519 as u64), + ) + .param( + coset::iana::OkpKeyParameter::X as i64, + coset::cbor::value::Value::from(pub_key), + ) + .algorithm(coset::iana::Algorithm::ECDH_ES_HKDF_256), + }; + + if let Some(key_id) = key_id { + builder = builder.key_id(key_id); + } + if test_mode == rpc::TestMode(true) { + builder = builder.param(RKP_TEST_KEY_CBOR_MARKER, coset::cbor::value::Value::Null); + } + Ok(builder.build()) + } +} + +/// Elliptic curve private key material. +#[derive(Clone, PartialEq, Eq)] +pub enum Key { + /// P-224 private key. + P224(NistKey), + /// P-256 private key. + P256(NistKey), + /// P-384 private key. + P384(NistKey), + /// P-521 private key. + P521(NistKey), + /// Ed25519 private key. + Ed25519(Ed25519Key), + /// X25519 private key. + X25519(X25519Key), +} + +/// Indication of the purpose for a COSE key. +pub enum CoseKeyPurpose { + /// ECDH key agreement. + Agree, + /// ECDSA signature generation. + Sign, +} + +impl Key { + /// Return the private key material. + pub fn private_key_bytes(&self) -> &[u8] { + match self { + Key::P224(key) => &key.0, + Key::P256(key) => &key.0, + Key::P384(key) => &key.0, + Key::P521(key) => &key.0, + Key::Ed25519(key) => &key.0, + Key::X25519(key) => &key.0, + } + } + + /// Return the type of curve. + pub fn curve_type(&self) -> CurveType { + match self { + Key::P224(_) | Key::P256(_) | Key::P384(_) | Key::P521(_) => CurveType::Nist, + Key::Ed25519(_) => CurveType::EdDsa, + Key::X25519(_) => CurveType::Xdh, + } + } + + /// Return the curve. + pub fn curve(&self) -> EcCurve { + match self { + Key::P224(_) => EcCurve::P224, + Key::P256(_) => EcCurve::P256, + Key::P384(_) => EcCurve::P384, + Key::P521(_) => EcCurve::P521, + Key::Ed25519(_) => EcCurve::Curve25519, + Key::X25519(_) => EcCurve::Curve25519, + } + } +} + +/// A NIST EC key, in the form of an ASN.1 DER encoding of a `ECPrivateKey` structure, +/// as specified by RFC 5915 section 3: +/// +/// ```asn1 +/// ECPrivateKey ::= SEQUENCE { +/// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), +/// privateKey OCTET STRING, +/// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, +/// publicKey [1] BIT STRING OPTIONAL +/// } +/// ``` +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct NistKey(pub Vec); + +/// Helper function to return the (x,y) coordinates, given the public key as a SEC-1 encoded +/// uncompressed point. 0x04: uncompressed, followed by x || y coordinates. +pub fn coordinates_from_pub_key( + pub_key: Vec, + curve: NistCurve, +) -> Result<(Vec, Vec), Error> { + let coord_len = curve.coord_len(); + if pub_key.len() != (1 + 2 * coord_len) { + return Err(km_err!( + UnsupportedKeySize, + "unexpected SEC1 pubkey len of {} for {:?}", + pub_key.len(), + curve + )); + } + if pub_key[0] != SEC1_UNCOMPRESSED_PREFIX { + return Err(km_err!( + UnsupportedKeySize, + "unexpected SEC1 pubkey initial byte {} for {:?}", + pub_key[0], + curve + )); + } + Ok((try_to_vec(&pub_key[1..1 + coord_len])?, try_to_vec(&pub_key[1 + coord_len..])?)) +} + +/// An Ed25519 private key. +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct Ed25519Key(pub [u8; CURVE25519_PRIV_KEY_LEN]); + +/// An X25519 private key. +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct X25519Key(pub [u8; CURVE25519_PRIV_KEY_LEN]); + +/// Return the OID used in an `AlgorithmIdentifier` for signatures produced by this curve. +pub fn curve_to_signing_oid(curve: EcCurve) -> pkcs8::ObjectIdentifier { + match curve { + EcCurve::P224 | EcCurve::P256 | EcCurve::P384 | EcCurve::P521 => ECDSA_SHA256_SIGNATURE_OID, + EcCurve::Curve25519 => X509_ED25519_OID, + } +} + +/// Return the key size for a curve. +pub fn curve_to_key_size(curve: EcCurve) -> KeySizeInBits { + KeySizeInBits(match curve { + EcCurve::P224 => 224, + EcCurve::P256 => 256, + EcCurve::P384 => 384, + EcCurve::P521 => 521, + EcCurve::Curve25519 => 256, + }) +} + +/// Import an NIST EC key in SEC1 ECPrivateKey format. +pub fn import_sec1_private_key(data: &[u8]) -> Result { + let ec_key = sec1::EcPrivateKey::from_der(data) + .map_err(|e| der_err!(e, "failed to parse ECPrivateKey"))?; + let ec_parameters = ec_key.parameters.ok_or_else(|| { + km_err!(InvalidArgument, "sec1 formatted EC private key didn't have a parameters field") + })?; + let parameters_oid = ec_parameters.named_curve().ok_or_else(|| { + km_err!( + InvalidArgument, + "couldn't retrieve parameters oid from sec1 ECPrivateKey formatted ec key parameters" + ) + })?; + let algorithm = + AlgorithmIdentifier { oid: X509_NIST_OID, parameters: Some(AnyRef::from(¶meters_oid)) }; + let pkcs8_key = pkcs8::PrivateKeyInfo::new(algorithm, data); + import_pkcs8_key_impl(&pkcs8_key) +} + +/// Import an EC key in PKCS#8 format. +pub fn import_pkcs8_key(data: &[u8]) -> Result { + let key_info = pkcs8::PrivateKeyInfo::try_from(data) + .map_err(|_| km_err!(InvalidArgument, "failed to parse PKCS#8 EC key"))?; + import_pkcs8_key_impl(&key_info) +} + +/// Import a `pkcs8::PrivateKeyInfo` EC key. +fn import_pkcs8_key_impl(key_info: &pkcs8::PrivateKeyInfo) -> Result { + let algo_params = key_info.algorithm.parameters; + match key_info.algorithm.oid { + X509_NIST_OID => { + let algo_params = algo_params.ok_or_else(|| { + km_err!( + InvalidArgument, + "missing PKCS#8 parameters for NIST curve import under OID {:?}", + key_info.algorithm.oid + ) + })?; + let curve_oid = algo_params + .decode_as() + .map_err(|_e| km_err!(InvalidArgument, "imported key has no OID parameter"))?; + let (curve, key) = match curve_oid { + ALGO_PARAM_P224_OID => { + (EcCurve::P224, Key::P224(NistKey(try_to_vec(key_info.private_key)?))) + } + ALGO_PARAM_P256_OID => { + (EcCurve::P256, Key::P256(NistKey(try_to_vec(key_info.private_key)?))) + } + ALGO_PARAM_P384_OID => { + (EcCurve::P384, Key::P384(NistKey(try_to_vec(key_info.private_key)?))) + } + ALGO_PARAM_P521_OID => { + (EcCurve::P521, Key::P521(NistKey(try_to_vec(key_info.private_key)?))) + } + oid => { + return Err(km_err!( + ImportParameterMismatch, + "imported key has unknown OID {:?}", + oid, + )) + } + }; + Ok(KeyMaterial::Ec(curve, CurveType::Nist, key.into())) + } + X509_ED25519_OID => { + if algo_params.is_some() { + Err(km_err!(InvalidArgument, "unexpected PKCS#8 parameters for Ed25519 import")) + } else { + // For Ed25519 the PKCS#8 `privateKey` field holds a `CurvePrivateKey` + // (RFC 8410 s7) that is an OCTET STRING holding the raw key. As this is DER, + // this is just a 2 byte prefix (0x04 = OCTET STRING, 0x20 = length of raw key). + if key_info.private_key.len() != 2 + CURVE25519_PRIV_KEY_LEN + || key_info.private_key[0] != 0x04 + || key_info.private_key[1] != 0x20 + { + return Err(km_err!(InvalidArgument, "unexpected CurvePrivateKey contents")); + } + import_raw_ed25519_key(&key_info.private_key[2..]) + } + } + X509_X25519_OID => { + if algo_params.is_some() { + Err(km_err!(InvalidArgument, "unexpected PKCS#8 parameters for X25519 import",)) + } else { + // For X25519 the PKCS#8 `privateKey` field holds a `CurvePrivateKey` + // (RFC 8410 s7) that is an OCTET STRING holding the raw key. As this is DER, + // this is just a 2 byte prefix (0x04 = OCTET STRING, 0x20 = length of raw key). + if key_info.private_key.len() != 2 + CURVE25519_PRIV_KEY_LEN + || key_info.private_key[0] != 0x04 + || key_info.private_key[1] != 0x20 + { + return Err(km_err!(InvalidArgument, "unexpected CurvePrivateKey contents")); + } + import_raw_x25519_key(&key_info.private_key[2..]) + } + } + _ => Err(km_err!( + InvalidArgument, + "unexpected OID {:?} for PKCS#8 EC key import", + key_info.algorithm.oid, + )), + } +} + +/// Import a 32-byte raw Ed25519 key. +pub fn import_raw_ed25519_key(data: &[u8]) -> Result { + let key = data.try_into().map_err(|_e| { + km_err!(InvalidInputLength, "import Ed25519 key of incorrect len {}", data.len()) + })?; + Ok(KeyMaterial::Ec(EcCurve::Curve25519, CurveType::EdDsa, Key::Ed25519(Ed25519Key(key)).into())) +} + +/// Import a 32-byte raw X25519 key. +pub fn import_raw_x25519_key(data: &[u8]) -> Result { + let key = data.try_into().map_err(|_e| { + km_err!(InvalidInputLength, "import X25519 key of incorrect len {}", data.len()) + })?; + Ok(KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, Key::X25519(X25519Key(key)).into())) +} + +/// Convert a signature as emitted from the `Ec` trait into the form needed for +/// a `COSE_Sign1`. +pub fn to_cose_signature(curve: EcCurve, sig: Vec) -> Result, Error> { + match curve { + EcCurve::P224 | EcCurve::P256 | EcCurve::P384 | EcCurve::P521 => { + // NIST curve signatures are emitted as a DER-encoded `SEQUENCE`. + let der_sig = NistSignature::from_der(&sig) + .map_err(|e| km_err!(EncodingError, "failed to parse DER signature: {:?}", e))?; + // COSE expects signature of (r||s) with each value left-padded with zeros to coordinate + // size. + let nist_curve = NistCurve::try_from(curve)?; + let l = nist_curve.coord_len(); + let mut sig = vec_try![0; 2 * l]?; + let r = der_sig.r.as_bytes(); + let s = der_sig.s.as_bytes(); + let r_offset = l - r.len(); + let s_offset = l + l - s.len(); + sig[r_offset..r_offset + r.len()].copy_from_slice(r); + sig[s_offset..s_offset + s.len()].copy_from_slice(s); + Ok(sig) + } + EcCurve::Curve25519 => { + // Ed25519 signatures can be used as-is (RFC 8410 section 6) + Ok(sig) + } + } +} + +/// Convert a signature as used in a `COSE_Sign1` into the form needed for the `Ec` trait. +pub fn from_cose_signature(curve: EcCurve, sig: &[u8]) -> Result, Error> { + match curve { + EcCurve::P224 | EcCurve::P256 | EcCurve::P384 | EcCurve::P521 => { + // COSE signatures are (r||s) with each value left-padded with zeros to coordinate size. + let nist_curve = NistCurve::try_from(curve)?; + let l = nist_curve.coord_len(); + if sig.len() != 2 * l { + return Err(km_err!( + EncodingError, + "unexpected len {} for {:?} COSE signature value", + sig.len(), + nist_curve + )); + } + + // NIST curve signatures need to be emitted as a DER-encoded `SEQUENCE`. + let der_sig = NistSignature { + r: der::asn1::UintRef::new(&sig[..l]) + .map_err(|e| km_err!(EncodingError, "failed to build INTEGER: {:?}", e))?, + s: der::asn1::UintRef::new(&sig[l..]) + .map_err(|e| km_err!(EncodingError, "failed to build INTEGER: {:?}", e))?, + }; + der_sig + .to_der() + .map_err(|e| km_err!(EncodingError, "failed to encode signature SEQUENCE: {:?}", e)) + } + EcCurve::Curve25519 => { + // Ed25519 signatures can be used as-is (RFC 8410 section 6) + try_to_vec(sig) + } + } +} + +/// DER-encoded signature from a NIST curve (RFC 3279 section 2.2.3): +/// ```asn1 +/// Ecdsa-Sig-Value ::= SEQUENCE { +/// r INTEGER, +/// s INTEGER +/// } +/// ``` +#[derive(Sequence)] +struct NistSignature<'a> { + r: der::asn1::UintRef<'a>, + s: der::asn1::UintRef<'a>, +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_sig_decode() { + let sig_data = hex::decode("3045022001b309d5eeffa5d550bde27630f9fc7f08492e4617bc158da08b913414cf675b022100fcdca2e77d036c33fa78f4a892b98569358d83c047a7d8a74ce6fe12fbf919c6").unwrap(); + let sig = NistSignature::from_der(&sig_data).expect("sequence should decode"); + assert_eq!( + hex::encode(sig.r.as_bytes()), + "01b309d5eeffa5d550bde27630f9fc7f08492e4617bc158da08b913414cf675b" + ); + assert_eq!( + hex::encode(sig.s.as_bytes()), + "fcdca2e77d036c33fa78f4a892b98569358d83c047a7d8a74ce6fe12fbf919c6" + ); + } + + #[test] + fn test_longer_sig_transmute() { + let nist_sig_data = hex::decode(concat!( + "30", // SEQUENCE + "45", // len + "02", // INTEGER + "20", // len = 32 + "01b309d5eeffa5d550bde27630f9fc7f08492e4617bc158da08b913414cf675b", + "02", // INTEGER + "21", // len = 33 (high bit set so leading zero needed) + "00fcdca2e77d036c33fa78f4a892b98569358d83c047a7d8a74ce6fe12fbf919c6" + )) + .unwrap(); + let cose_sig_data = to_cose_signature(EcCurve::P256, nist_sig_data.clone()).unwrap(); + assert_eq!( + concat!( + "01b309d5eeffa5d550bde27630f9fc7f08492e4617bc158da08b913414cf675b", + "fcdca2e77d036c33fa78f4a892b98569358d83c047a7d8a74ce6fe12fbf919c6" + ), + hex::encode(&cose_sig_data), + ); + let got_nist_sig = from_cose_signature(EcCurve::P256, &cose_sig_data).unwrap(); + assert_eq!(got_nist_sig, nist_sig_data); + } + + #[test] + fn test_short_sig_transmute() { + let nist_sig_data = hex::decode(concat!( + "30", // SEQUENCE + "43", // len x44 + "02", // INTEGER + "1e", // len = 30 + "09d5eeffa5d550bde27630f9fc7f08492e4617bc158da08b913414cf675b", + "02", // INTEGER + "21", // len = 33 (high bit set so leading zero needed) + "00fcdca2e77d036c33fa78f4a892b98569358d83c047a7d8a74ce6fe12fbf919c6" + )) + .unwrap(); + let cose_sig_data = to_cose_signature(EcCurve::P256, nist_sig_data.clone()).unwrap(); + assert_eq!( + concat!( + "000009d5eeffa5d550bde27630f9fc7f08492e4617bc158da08b913414cf675b", + "fcdca2e77d036c33fa78f4a892b98569358d83c047a7d8a74ce6fe12fbf919c6" + ), + hex::encode(&cose_sig_data), + ); + let got_nist_sig = from_cose_signature(EcCurve::P256, &cose_sig_data).unwrap(); + assert_eq!(got_nist_sig, nist_sig_data); + } + + #[test] + fn test_sec1_ec_import() { + // Key data created with: + // ``` + // openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem + // ``` + let key_data = hex::decode(concat!( + "3077", // SEQUENCE len x77 (ECPrivateKey) + "020101", // INTEGER 1 = (ecPrivkeyVer1) + "0420", // OCTET STRING len x20 (privateKey) + "a6a30ca3dc87b58763736400e7e86260", + "9e8311f41e6b89888c33753218168517", + "a00a", // [0] len x0a (parameters) + "0608", // OBJECT IDENTIFIER len 8 (NamedCurve) + "2a8648ce3d030107", // 1.2.840.10045.3.1.7=secp256r1 + "a144", // [1] len x44 (publicKey) + "0342", // BIT STRING len x42 + "00", // no pad bits + "0481e4ce20d8be3dd40b940b3a3ba3e8", + "cf5a3f2156eceb4debb8fce83cbe4a48", + "bd576a03eebf77d329a438fcdc509f37", + "1f092cad41e2ecf9f25cd82f31500f33", + "8e" + )) + .unwrap(); + let key = import_sec1_private_key(&key_data).expect("SEC1 parse failed"); + if let KeyMaterial::Ec(curve, curve_type, _key) = key { + assert_eq!(curve, EcCurve::P256); + assert_eq!(curve_type, CurveType::Nist); + } else { + panic!("unexpected key type"); + } + } +} diff --git a/libs/rust/common/src/crypto/hmac.rs b/libs/rust/common/src/crypto/hmac.rs new file mode 100644 index 0000000..745c066 --- /dev/null +++ b/libs/rust/common/src/crypto/hmac.rs @@ -0,0 +1,69 @@ +// Copyright 2022, 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. + +//! Functionality related to HMAC signing/verification. + +use crate::{km_err, try_to_vec, Error}; +use kmr_wire::KeySizeInBits; +use zeroize::ZeroizeOnDrop; + +/// Minimum size of an HMAC key in bits. +pub const MIN_KEY_SIZE_BITS: usize = 64; + +/// Maximum size of a StrongBox HMAC key in bits. +pub const MAX_STRONGBOX_KEY_SIZE_BITS: usize = 512; + +/// Maximum size of a HMAC key in bits. +pub const MAX_KEY_SIZE_BITS: usize = 1024; + +/// An HMAC key. +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct Key(pub Vec); + +fn valid_size(key_size: KeySizeInBits, max_size_bits: usize) -> Result<(), Error> { + if key_size.0 % 8 != 0 { + Err(km_err!(UnsupportedKeySize, "key size {} bits not a multiple of 8", key_size.0)) + } else if !(MIN_KEY_SIZE_BITS..=max_size_bits).contains(&(key_size.0 as usize)) { + Err(km_err!(UnsupportedKeySize, "unsupported KEY_SIZE {} bits for HMAC", key_size.0)) + } else { + Ok(()) + } +} + +/// Check that the size of an HMAC key is within the allowed size for the KeyMint HAL. +pub fn valid_hal_size(key_size: KeySizeInBits) -> Result<(), Error> { + valid_size(key_size, MAX_KEY_SIZE_BITS) +} + +/// Check that the size of an HMAC key is within the allowed size for a StrongBox implementation. +pub fn valid_strongbox_hal_size(key_size: KeySizeInBits) -> Result<(), Error> { + valid_size(key_size, MAX_STRONGBOX_KEY_SIZE_BITS) +} + +impl Key { + /// Create a new HMAC key from data. + pub fn new(data: Vec) -> Key { + Key(data) + } + + /// Create a new HMAC key from data. + pub fn new_from(data: &[u8]) -> Result { + Ok(Key::new(try_to_vec(data)?)) + } + + /// Indicate the size of the key in bits. + pub fn size(&self) -> KeySizeInBits { + KeySizeInBits((self.0.len() * 8) as u32) + } +} diff --git a/libs/rust/common/src/crypto/rsa.rs b/libs/rust/common/src/crypto/rsa.rs new file mode 100644 index 0000000..1ba59cb --- /dev/null +++ b/libs/rust/common/src/crypto/rsa.rs @@ -0,0 +1,249 @@ +// Copyright 2022, 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. + +//! Functionality related to RSA. + +use super::{KeyMaterial, KeySizeInBits, OpaqueOr, RsaExponent}; +use crate::{der_err, km_err, tag, try_to_vec, Error, FallibleAllocExt}; +use der::{asn1::BitStringRef, Decode, Encode}; +use kmr_wire::keymint::{Digest, KeyParam, PaddingMode}; +use pkcs1::RsaPrivateKey; +use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo, SubjectPublicKeyInfoRef}; +use zeroize::ZeroizeOnDrop; + +/// Overhead for PKCS#1 v1.5 signature padding of undigested messages. Digested messages have +/// additional overhead, for the digest algorithmIdentifier required by PKCS#1. +pub const PKCS1_UNDIGESTED_SIGNATURE_PADDING_OVERHEAD: usize = 11; + +/// OID value for PKCS#1-encoded RSA keys held in PKCS#8 and X.509; see RFC 3447 A.1. +pub const X509_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); + +/// OID value for PKCS#1 signature with SHA-256 and RSA, see RFC 4055 s5. +pub const SHA256_PKCS1_SIGNATURE_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11"); + +/// An RSA key, in the form of an ASN.1 DER encoding of an PKCS#1 `RSAPrivateKey` structure, +/// as specified by RFC 3447 sections A.1.2 and 3.2: +/// +/// ```asn1 +/// RSAPrivateKey ::= SEQUENCE { +/// version Version, +/// modulus INTEGER, -- n +/// publicExponent INTEGER, -- e +/// privateExponent INTEGER, -- d +/// prime1 INTEGER, -- p +/// prime2 INTEGER, -- q +/// exponent1 INTEGER, -- d mod (p-1) +/// exponent2 INTEGER, -- d mod (q-1) +/// coefficient INTEGER, -- (inverse of q) mod p +/// otherPrimeInfos OtherPrimeInfos OPTIONAL +/// } +/// +/// OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo +/// +/// OtherPrimeInfo ::= SEQUENCE { +/// prime INTEGER, -- ri +/// exponent INTEGER, -- di +/// coefficient INTEGER -- ti +/// } +/// ``` +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct Key(pub Vec); + +impl Key { + /// Return the `subjectPublicKey` that holds an ASN.1 DER-encoded `SEQUENCE` + /// as per RFC 3279 section 2.3.1: + /// ```asn1 + /// RSAPublicKey ::= SEQUENCE { + /// modulus INTEGER, -- n + /// publicExponent INTEGER } -- e + /// ``` + pub fn subject_public_key(&self) -> Result, Error> { + let rsa_pvt_key = RsaPrivateKey::from_der(self.0.as_slice()) + .map_err(|e| der_err!(e, "failed to parse RsaPrivateKey"))?; + + let rsa_pub_key = rsa_pvt_key.public_key(); + let mut encoded_data = Vec::::new(); + rsa_pub_key + .encode_to_vec(&mut encoded_data) + .map_err(|e| der_err!(e, "failed to encode RSA PublicKey"))?; + Ok(encoded_data) + } + + /// Size of the key in bytes. + pub fn size(&self) -> usize { + let rsa_pvt_key = match RsaPrivateKey::from_der(self.0.as_slice()) { + Ok(k) => k, + Err(e) => { + log::error!("failed to determine RSA key length: {:?}", e); + return 0; + } + }; + let len = u32::from(rsa_pvt_key.modulus.len()); + len as usize + } +} + +impl OpaqueOr { + /// Encode into `buf` the public key information as an ASN.1 DER encodable + /// `SubjectPublicKeyInfo`, as described in RFC 5280 section 4.1. + /// + /// ```asn1 + /// SubjectPublicKeyInfo ::= SEQUENCE { + /// algorithm AlgorithmIdentifier, + /// subjectPublicKey BIT STRING } + /// + /// AlgorithmIdentifier ::= SEQUENCE { + /// algorithm OBJECT IDENTIFIER, + /// parameters ANY DEFINED BY algorithm OPTIONAL } + /// ``` + /// + /// For RSA keys, the contents are described in RFC 3279 section 2.3.1. + /// + /// - The `AlgorithmIdentifier` has an algorithm OID of 1.2.840.113549.1.1.1. + /// - The `AlgorithmIdentifier` has `NULL` parameters. + /// - The `subjectPublicKey` bit string holds an ASN.1 DER-encoded `SEQUENCE`: + /// ```asn1 + /// RSAPublicKey ::= SEQUENCE { + /// modulus INTEGER, -- n + /// publicExponent INTEGER } -- e + /// ``` + pub fn subject_public_key_info<'a>( + &'a self, + buf: &'a mut Vec, + rsa: &dyn super::Rsa, + ) -> Result, Error> { + let pub_key = rsa.subject_public_key(self)?; + buf.try_extend_from_slice(&pub_key)?; + Ok(SubjectPublicKeyInfo { + algorithm: AlgorithmIdentifier { oid: X509_OID, parameters: Some(der::AnyRef::NULL) }, + subject_public_key: BitStringRef::from_bytes(buf) + .map_err(|e| km_err!(UnknownError, "invalid bitstring: {e:?}"))?, + }) + } +} + +/// RSA decryption mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecryptionMode { + /// No padding. + NoPadding, + /// RSA-OAEP padding. + OaepPadding { + /// Digest to use for the message + msg_digest: Digest, + /// Digest to use in the MGF1 function. + mgf_digest: Digest, + }, + /// PKCS#1 v1.5 padding. + Pkcs1_1_5Padding, +} + +impl DecryptionMode { + /// Determine the [`DecryptionMode`] from parameters. + pub fn new(params: &[KeyParam]) -> Result { + let padding = tag::get_padding_mode(params)?; + match padding { + PaddingMode::None => Ok(DecryptionMode::NoPadding), + PaddingMode::RsaOaep => { + let msg_digest = tag::get_digest(params)?; + let mgf_digest = tag::get_mgf_digest(params)?; + Ok(DecryptionMode::OaepPadding { msg_digest, mgf_digest }) + } + PaddingMode::RsaPkcs115Encrypt => Ok(DecryptionMode::Pkcs1_1_5Padding), + _ => Err(km_err!( + UnsupportedPaddingMode, + "padding mode {:?} not supported for RSA decryption", + padding + )), + } + } +} + +/// RSA signature mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SignMode { + /// No padding. + NoPadding, + /// RSA-PSS signature scheme using the given digest. + PssPadding(Digest), + /// PKCS#1 v1.5 padding using the given digest. + Pkcs1_1_5Padding(Digest), +} + +impl SignMode { + /// Determine the [`SignMode`] from parameters. + pub fn new(params: &[KeyParam]) -> Result { + let padding = tag::get_padding_mode(params)?; + match padding { + PaddingMode::None => Ok(SignMode::NoPadding), + PaddingMode::RsaPss => { + let digest = tag::get_digest(params)?; + Ok(SignMode::PssPadding(digest)) + } + PaddingMode::RsaPkcs115Sign => { + let digest = tag::get_digest(params)?; + Ok(SignMode::Pkcs1_1_5Padding(digest)) + } + _ => Err(km_err!( + UnsupportedPaddingMode, + "padding mode {:?} not supported for RSA signing", + padding + )), + } + } +} + +/// Import an RSA key in PKCS#8 format, also returning the key size in bits and public exponent. +pub fn import_pkcs8_key(data: &[u8]) -> Result<(KeyMaterial, KeySizeInBits, RsaExponent), Error> { + let key_info = pkcs8::PrivateKeyInfo::try_from(data) + .map_err(|_| km_err!(InvalidArgument, "failed to parse PKCS#8 RSA key"))?; + if key_info.algorithm.oid != X509_OID { + return Err(km_err!( + InvalidArgument, + "unexpected OID {:?} for PKCS#1 RSA key import", + key_info.algorithm.oid + )); + } + // For RSA, the inner private key is an ASN.1 `RSAPrivateKey`, as per PKCS#1 (RFC 3447 A.1.2). + import_pkcs1_key(key_info.private_key) +} + +/// Import an RSA key in PKCS#1 format, also returning the key size in bits and public exponent. +pub fn import_pkcs1_key( + private_key: &[u8], +) -> Result<(KeyMaterial, KeySizeInBits, RsaExponent), Error> { + let key = Key(try_to_vec(private_key)?); + + // Need to parse it to find size/exponent. + let parsed_key = pkcs1::RsaPrivateKey::try_from(private_key) + .map_err(|_| km_err!(InvalidArgument, "failed to parse inner PKCS#1 key"))?; + let key_size = parsed_key.modulus.as_bytes().len() as u32 * 8; + + let pub_exponent_bytes = parsed_key.public_exponent.as_bytes(); + if pub_exponent_bytes.len() > 8 { + return Err(km_err!( + InvalidArgument, + "public exponent of length {} too big", + pub_exponent_bytes.len() + )); + } + let offset = 8 - pub_exponent_bytes.len(); + let mut pub_exponent_arr = [0u8; 8]; + pub_exponent_arr[offset..].copy_from_slice(pub_exponent_bytes); + let pub_exponent = u64::from_be_bytes(pub_exponent_arr); + + Ok((KeyMaterial::Rsa(key.into()), KeySizeInBits(key_size), RsaExponent(pub_exponent))) +} diff --git a/libs/rust/common/src/crypto/traits.rs b/libs/rust/common/src/crypto/traits.rs new file mode 100644 index 0000000..5c9d0eb --- /dev/null +++ b/libs/rust/common/src/crypto/traits.rs @@ -0,0 +1,742 @@ +// Copyright 2022, 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. + +//! Traits representing abstractions of cryptographic functionality. +use super::*; +use crate::{crypto::ec::Key, der_err, explicit, keyblob, vec_try, Error}; +use der::Decode; +use kmr_wire::{keymint, keymint::Digest, KeySizeInBits, RsaExponent}; +use log::{error, warn}; + +/// Combined collection of trait implementations that must be provided. +pub struct Implementation { + /// Random number generator. + pub rng: Box, + + /// A local clock, if available. If not available, KeyMint will require timestamp tokens to + /// be provided by an external `ISecureClock` (with which it shares a common key). + pub clock: Option>, + + /// A constant-time equality implementation. + pub compare: Box, + + /// AES implementation. + pub aes: Box, + + /// DES implementation. + pub des: Box, + + /// HMAC implementation. + pub hmac: Box, + + /// RSA implementation. + pub rsa: Box, + + /// EC implementation. + pub ec: Box, + + /// CKDF implementation. + pub ckdf: Box, + + /// HKDF implementation. + pub hkdf: Box, + + /// SHA-256 implementation. + pub sha256: Box, +} + +/// Abstraction of a random number generator that is cryptographically secure +/// and which accepts additional entropy to be mixed in. +pub trait Rng: Send { + /// Add entropy to the generator's pool. + fn add_entropy(&mut self, data: &[u8]); + /// Generate random data. + fn fill_bytes(&mut self, dest: &mut [u8]); + /// Return a random `u64` value. + fn next_u64(&mut self) -> u64 { + let mut buf = [0u8; 8]; + self.fill_bytes(&mut buf); + u64::from_le_bytes(buf) + } +} + +/// Abstraction of constant-time comparisons, for use in cryptographic contexts where timing attacks +/// need to be avoided. +pub trait ConstTimeEq: Send { + /// Indicate whether arguments are the same. + fn eq(&self, left: &[u8], right: &[u8]) -> bool; + /// Indicate whether arguments are the different. + fn ne(&self, left: &[u8], right: &[u8]) -> bool { + !self.eq(left, right) + } +} + +/// Abstraction of a monotonic clock. +pub trait MonotonicClock: Send { + /// Return the current time in milliseconds since some arbitrary point in time. Time must be + /// monotonically increasing, and "current time" must not repeat until the Android device + /// reboots, or until at least 50 million years have elapsed. Time must also continue to + /// advance while the device is suspended (which may not be the case with e.g. Linux's + /// `clock_gettime(CLOCK_MONOTONIC)`). + fn now(&self) -> MillisecondsSinceEpoch; +} + +/// Abstraction of AES functionality. +pub trait Aes: Send { + /// Generate an AES key. The default implementation fills with random data. Key generation + /// parameters are passed in for reference, to allow for implementations that might have + /// parameter-specific behaviour. + fn generate_key( + &self, + rng: &mut dyn Rng, + variant: aes::Variant, + _params: &[keymint::KeyParam], + ) -> Result { + Ok(match variant { + aes::Variant::Aes128 => { + let mut key = [0; 16]; + rng.fill_bytes(&mut key[..]); + KeyMaterial::Aes(aes::Key::Aes128(key).into()) + } + aes::Variant::Aes192 => { + let mut key = [0; 24]; + rng.fill_bytes(&mut key[..]); + KeyMaterial::Aes(aes::Key::Aes192(key).into()) + } + aes::Variant::Aes256 => { + let mut key = [0; 32]; + rng.fill_bytes(&mut key[..]); + KeyMaterial::Aes(aes::Key::Aes256(key).into()) + } + }) + } + + /// Import an AES key, also returning the key size in bits. Key import parameters are passed in + /// for reference, to allow for implementations that might have parameter-specific behaviour. + fn import_key( + &self, + data: &[u8], + _params: &[keymint::KeyParam], + ) -> Result<(KeyMaterial, KeySizeInBits), Error> { + let aes_key = aes::Key::new_from(data)?; + let key_size = aes_key.size(); + Ok((KeyMaterial::Aes(aes_key.into()), key_size)) + } + + /// Create an AES operation. For block mode operations with no padding + /// ([`aes::CipherMode::EcbNoPadding`] and [`aes::CipherMode::CbcNoPadding`]) the operation + /// implementation should reject (with `ErrorCode::InvalidInputLength`) input data that does + /// not end up being a multiple of the block size. + fn begin( + &self, + key: OpaqueOr, + mode: aes::CipherMode, + dir: SymmetricOperation, + ) -> Result, Error>; + + /// Create an AES-GCM operation. + fn begin_aead( + &self, + key: OpaqueOr, + mode: aes::GcmMode, + dir: SymmetricOperation, + ) -> Result, Error>; +} + +/// Abstraction of 3-DES functionality. +pub trait Des: Send { + /// Generate a triple DES key. Key generation parameters are passed in for reference, to allow + /// for implementations that might have parameter-specific behaviour. + fn generate_key( + &self, + rng: &mut dyn Rng, + _params: &[keymint::KeyParam], + ) -> Result { + let mut key = vec_try![0; 24]?; + // Note: parity bits must be ignored. + rng.fill_bytes(&mut key[..]); + Ok(KeyMaterial::TripleDes(des::Key::new(key)?.into())) + } + + /// Import a triple DES key. Key import parameters are passed in for reference, to allow for + /// implementations that might have parameter-specific behaviour. + fn import_key(&self, data: &[u8], _params: &[keymint::KeyParam]) -> Result { + let des_key = des::Key::new_from(data)?; + Ok(KeyMaterial::TripleDes(des_key.into())) + } + + /// Create a DES operation. For block mode operations with no padding + /// ([`des::Mode::EcbNoPadding`] and [`des::Mode::CbcNoPadding`]) the operation implementation + /// should reject (with `ErrorCode::InvalidInputLength`) input data that does not end up being + /// a multiple of the block size. + fn begin( + &self, + key: OpaqueOr, + mode: des::Mode, + dir: SymmetricOperation, + ) -> Result, Error>; +} + +/// Abstraction of HMAC functionality. +pub trait Hmac: Send { + /// Generate an HMAC key. Key generation parameters are passed in for reference, to allow for + /// implementations that might have parameter-specific behaviour. + fn generate_key( + &self, + rng: &mut dyn Rng, + key_size: KeySizeInBits, + _params: &[keymint::KeyParam], + ) -> Result { + hmac::valid_hal_size(key_size)?; + + let key_len = (key_size.0 / 8) as usize; + let mut key = vec_try![0; key_len]?; + rng.fill_bytes(&mut key); + Ok(KeyMaterial::Hmac(hmac::Key::new(key).into())) + } + + /// Import an HMAC key, also returning the key size in bits. Key import parameters are passed in + /// for reference, to allow for implementations that might have parameter-specific behaviour. + fn import_key( + &self, + data: &[u8], + _params: &[keymint::KeyParam], + ) -> Result<(KeyMaterial, KeySizeInBits), Error> { + let hmac_key = hmac::Key::new_from(data)?; + let key_size = hmac_key.size(); + hmac::valid_hal_size(key_size)?; + Ok((KeyMaterial::Hmac(hmac_key.into()), key_size)) + } + + /// Create an HMAC operation. Implementations can assume that: + /// - `key` will have length in range `8..=64` bytes. + /// - `digest` will not be [`Digest::None`] + fn begin( + &self, + key: OpaqueOr, + digest: Digest, + ) -> Result, Error>; +} + +/// Abstraction of AES-CMAC functionality. (Note that this is not exposed in the KeyMint HAL API +/// directly, but is required for the CKDF operations involved in `ISharedSecret` negotiation.) +pub trait AesCmac: Send { + /// Create an AES-CMAC operation. Implementations can assume that `key` will have length + /// of either 16 (AES-128) or 32 (AES-256). + fn begin(&self, key: OpaqueOr) -> Result, Error>; +} + +/// Abstraction of RSA functionality. +pub trait Rsa: Send { + /// Generate an RSA key. Key generation parameters are passed in for reference, to allow for + /// implementations that might have parameter-specific behaviour. + fn generate_key( + &self, + rng: &mut dyn Rng, + key_size: KeySizeInBits, + pub_exponent: RsaExponent, + params: &[keymint::KeyParam], + ) -> Result; + + /// Import an RSA key in PKCS#8 format, also returning the key size in bits and public exponent. + /// Key import parameters are passed in for reference, to allow for implementations that might + /// have parameter-specific behaviour. + fn import_pkcs8_key( + &self, + data: &[u8], + _params: &[keymint::KeyParam], + ) -> Result<(KeyMaterial, KeySizeInBits, RsaExponent), Error> { + rsa::import_pkcs8_key(data) + } + + /// Return the public key data corresponds to the provided private `key`, + /// as an ASN.1 DER-encoded `SEQUENCE` as per RFC 3279 section 2.3.1: + /// ```asn1 + /// RSAPublicKey ::= SEQUENCE { + /// modulus INTEGER, -- n + /// publicExponent INTEGER } -- e + /// ``` + /// which is the `subjectPublicKey` to be included in `SubjectPublicKeyInfo`. + fn subject_public_key(&self, key: &OpaqueOr) -> Result, Error> { + // The default implementation only handles the `Explicit` variant. + let rsa_key = explicit!(key)?; + rsa_key.subject_public_key() + } + + /// Create an RSA decryption operation. + fn begin_decrypt( + &self, + key: OpaqueOr, + mode: rsa::DecryptionMode, + ) -> Result, Error>; + + /// Create an RSA signing operation. For [`rsa::SignMode::Pkcs1_1_5Padding(Digest::None)`] the + /// implementation should reject (with `ErrorCode::InvalidInputLength`) accumulated input that + /// is larger than the size of the RSA key less overhead + /// ([`rsa::PKCS1_UNDIGESTED_SIGNATURE_PADDING_OVERHEAD`]). + fn begin_sign( + &self, + key: OpaqueOr, + mode: rsa::SignMode, + ) -> Result, Error>; +} + +/// Abstraction of EC functionality. +pub trait Ec: Send { + /// Generate an EC key for a NIST curve. Key generation parameters are passed in for reference, + /// to allow for implementations that might have parameter-specific behaviour. + fn generate_nist_key( + &self, + rng: &mut dyn Rng, + curve: ec::NistCurve, + params: &[keymint::KeyParam], + ) -> Result; + + /// Generate an Ed25519 key. Key generation parameters are passed in for reference, to allow + /// for implementations that might have parameter-specific behaviour. + fn generate_ed25519_key( + &self, + rng: &mut dyn Rng, + params: &[keymint::KeyParam], + ) -> Result; + + /// Generate an X25519 key. Key generation parameters are passed in for reference, to allow for + /// implementations that might have parameter-specific behaviour. + fn generate_x25519_key( + &self, + rng: &mut dyn Rng, + params: &[keymint::KeyParam], + ) -> Result; + + /// Import an EC key in PKCS#8 format. Key import parameters are passed in for reference, to + /// allow for implementations that might have parameter-specific behaviour. + fn import_pkcs8_key( + &self, + data: &[u8], + _params: &[keymint::KeyParam], + ) -> Result { + ec::import_pkcs8_key(data) + } + + /// Import a 32-byte raw Ed25519 key. Key import parameters are passed in for reference, to + /// allow for implementations that might have parameter-specific behaviour. + fn import_raw_ed25519_key( + &self, + data: &[u8], + _params: &[keymint::KeyParam], + ) -> Result { + ec::import_raw_ed25519_key(data) + } + + /// Import a 32-byte raw X25519 key. Key import parameters are passed in for reference, to + /// allow for implementations that might have parameter-specific behaviour. + fn import_raw_x25519_key( + &self, + data: &[u8], + _params: &[keymint::KeyParam], + ) -> Result { + ec::import_raw_x25519_key(data) + } + + /// Return the public key data that corresponds to the provided private `key`. + /// If `CurveType` of the key is `CurveType::Nist`, return the public key data + /// as a SEC-1 encoded uncompressed point as described in RFC 5480 section 2.1. + /// I.e. 0x04: uncompressed, followed by x || y coordinates. + /// + /// For other two curve types, return the raw public key data. + fn subject_public_key(&self, key: &OpaqueOr) -> Result, Error> { + // The default implementation only handles the `Explicit` variant. + let ec_key = explicit!(key)?; + match ec_key { + Key::P224(nist_key) + | Key::P256(nist_key) + | Key::P384(nist_key) + | Key::P521(nist_key) => { + let ec_pvt_key = sec1::EcPrivateKey::from_der(nist_key.0.as_slice()) + .map_err(|e| der_err!(e, "failed to parse DER NIST EC PrivateKey"))?; + match ec_pvt_key.public_key { + Some(pub_key) => Ok(pub_key.to_vec()), + None => { + // Key structure doesn't include optional public key, so regenerate it. + let nist_curve: ec::NistCurve = ec_key.curve().try_into()?; + Ok(self.nist_public_key(nist_key, nist_curve)?) + } + } + } + Key::Ed25519(ed25519_key) => self.ed25519_public_key(ed25519_key), + Key::X25519(x25519_key) => self.x25519_public_key(x25519_key), + } + } + + /// Return the public key data that corresponds to the provided private `key`, as a SEC-1 + /// encoded uncompressed point. + fn nist_public_key(&self, key: &ec::NistKey, curve: ec::NistCurve) -> Result, Error>; + + /// Return the raw public key data that corresponds to the provided private `key`. + fn ed25519_public_key(&self, key: &ec::Ed25519Key) -> Result, Error>; + + /// Return the raw public key data that corresponds to the provided private `key`. + fn x25519_public_key(&self, key: &ec::X25519Key) -> Result, Error>; + + /// Create an EC key agreement operation. + /// The accumulated input for the operation is expected to be the peer's + /// public key, provided as an ASN.1 DER-encoded `SubjectPublicKeyInfo`. + fn begin_agree(&self, key: OpaqueOr) -> Result, Error>; + + /// Create an EC signing operation. For Ed25519 signing operations, the implementation should + /// reject (with `ErrorCode::InvalidInputLength`) accumulated data that is larger than + /// [`ec::MAX_ED25519_MSG_SIZE`]. + fn begin_sign( + &self, + key: OpaqueOr, + digest: Digest, + ) -> Result, Error>; +} + +/// Abstraction of an in-progress operation that emits data as it progresses. +pub trait EmittingOperation: Send { + /// Update operation with data. + fn update(&mut self, data: &[u8]) -> Result, Error>; + + /// Complete operation, consuming `self`. + fn finish(self: Box) -> Result, Error>; +} + +/// Abstraction of an in-progress operation that has authenticated associated data. +pub trait AadOperation: EmittingOperation { + /// Update additional data. Implementations can assume that all calls to `update_aad()` + /// will occur before any calls to `update()` or `finish()`. + fn update_aad(&mut self, aad: &[u8]) -> Result<(), Error>; +} + +/// Abstraction of an in-progress operation that only emits data when it completes. +pub trait AccumulatingOperation: Send { + /// Maximum size of accumulated input. + fn max_input_size(&self) -> Option { + None + } + + /// Update operation with data. + fn update(&mut self, data: &[u8]) -> Result<(), Error>; + + /// Complete operation, consuming `self`. + fn finish(self: Box) -> Result, Error>; +} + +/// Abstraction of HKDF key derivation with HMAC-SHA256. +/// +/// A default implementation of this trait is available (in `crypto.rs`) for any type that +/// implements [`Hmac`]. +pub trait Hkdf: Send { + /// Perform combined HKDF using the input key material in `ikm`. + fn hkdf(&self, salt: &[u8], ikm: &[u8], info: &[u8], out_len: usize) -> Result, Error> { + let prk = self.extract(salt, ikm)?; + self.expand(&prk, info, out_len) + } + + /// Perform the HKDF-Extract step on the input key material in `ikm`, using optional `salt`. + fn extract(&self, salt: &[u8], ikm: &[u8]) -> Result, Error>; + + /// Perform the HKDF-Expand step using the pseudo-random key in `prk`. + fn expand( + &self, + prk: &OpaqueOr, + info: &[u8], + out_len: usize, + ) -> Result, Error>; + + /// Perform combined HKDF using the input key material in `ikm`, emitting output in the form of + /// an AES key. + fn hkdf_aes( + &self, + salt: &[u8], + ikm: &[u8], + info: &[u8], + variant: aes::Variant, + ) -> Result, Error> { + // Default implementation generates explicit key material and converts to an [`aes::Key`]. + let data = self.hkdf(salt, ikm, info, variant.key_size())?; + let explicit_key = aes::Key::new(data)?; + Ok(explicit_key.into()) + } + + /// Perform the HKDF-Expand step using the pseudo-random key in `prk`, emitting output in the + /// form of an AES key. + fn expand_aes( + &self, + prk: &OpaqueOr, + info: &[u8], + variant: aes::Variant, + ) -> Result, Error> { + // Default implementation generates explicit key material and converts to an [`aes::Key`]. + let data = self.expand(prk, info, variant.key_size())?; + let explicit_key = aes::Key::new(data)?; + Ok(explicit_key.into()) + } +} + +/// Abstraction of CKDF key derivation with AES-CMAC KDF from NIST SP 800-108 in counter mode (see +/// section 5.1). +/// +/// Aa default implementation of this trait is available (in `crypto.rs`) for any type that +/// implements [`AesCmac`]. +pub trait Ckdf: Send { + /// Perform CKDF using the key material in `key`. + fn ckdf( + &self, + key: &OpaqueOr, + label: &[u8], + chunks: &[&[u8]], + out_len: usize, + ) -> Result, Error>; +} + +/// Abstraction for SHA-256 hashing. +pub trait Sha256: Send { + /// Generate the SHA-256 input of `data`. + fn hash(&self, data: &[u8]) -> Result<[u8; 32], Error>; +} + +//////////////////////////////////////////////////////////// +// No-op implementations of traits. These implementations are +// only intended for convenience during the process of porting +// the KeyMint code to a new environment. + +/// Macro to emit an error log indicating that an unimplemented function +/// has been invoked (and where it is). +#[macro_export] +macro_rules! log_unimpl { + () => { + error!("{}:{}: Unimplemented placeholder KeyMint trait method invoked!", file!(), line!(),); + }; +} + +/// Mark a method as unimplemented (log error, return `ErrorCode::Unimplemented`) +#[macro_export] +macro_rules! unimpl { + () => { + log_unimpl!(); + return Err(Error::Hal( + kmr_wire::keymint::ErrorCode::Unimplemented, + format!("{}:{}: method unimplemented", file!(), line!()), + )); + }; +} + +/// Stub implementation of [`Rng`]. +pub struct NoOpRng; +impl Rng for NoOpRng { + fn add_entropy(&mut self, _data: &[u8]) { + log_unimpl!(); + } + fn fill_bytes(&mut self, _dest: &mut [u8]) { + log_unimpl!(); + } +} + +/// Stub implementation of [`ConstTimeEq`]. +#[derive(Clone)] +pub struct InsecureEq; +impl ConstTimeEq for InsecureEq { + fn eq(&self, left: &[u8], right: &[u8]) -> bool { + warn!("Insecure comparison operation performed"); + left == right + } +} + +/// Stub implementation of [`MonotonicClock`]. +pub struct NoOpClock; +impl MonotonicClock for NoOpClock { + fn now(&self) -> MillisecondsSinceEpoch { + log_unimpl!(); + MillisecondsSinceEpoch(0) + } +} + +/// Stub implementation of [`Aes`]. +pub struct NoOpAes; +impl Aes for NoOpAes { + fn begin( + &self, + _key: OpaqueOr, + _mode: aes::CipherMode, + _dir: SymmetricOperation, + ) -> Result, Error> { + unimpl!(); + } + fn begin_aead( + &self, + _key: OpaqueOr, + _mode: aes::GcmMode, + _dir: SymmetricOperation, + ) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`Des`]. +pub struct NoOpDes; +impl Des for NoOpDes { + fn begin( + &self, + _key: OpaqueOr, + _mode: des::Mode, + _dir: SymmetricOperation, + ) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`Hmac`]. +pub struct NoOpHmac; +impl Hmac for NoOpHmac { + fn begin( + &self, + _key: OpaqueOr, + _digest: Digest, + ) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`AesCmac`]. +pub struct NoOpAesCmac; +impl AesCmac for NoOpAesCmac { + fn begin(&self, _key: OpaqueOr) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`Rsa`]. +pub struct NoOpRsa; +impl Rsa for NoOpRsa { + fn generate_key( + &self, + _rng: &mut dyn Rng, + _key_size: KeySizeInBits, + _pub_exponent: RsaExponent, + _params: &[keymint::KeyParam], + ) -> Result { + unimpl!(); + } + + fn begin_decrypt( + &self, + _key: OpaqueOr, + _mode: rsa::DecryptionMode, + ) -> Result, Error> { + unimpl!(); + } + + fn begin_sign( + &self, + _key: OpaqueOr, + _mode: rsa::SignMode, + ) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`Ec`]. +pub struct NoOpEc; +impl Ec for NoOpEc { + fn generate_nist_key( + &self, + _rng: &mut dyn Rng, + _curve: ec::NistCurve, + _params: &[keymint::KeyParam], + ) -> Result { + unimpl!(); + } + + fn generate_ed25519_key( + &self, + _rng: &mut dyn Rng, + _params: &[keymint::KeyParam], + ) -> Result { + unimpl!(); + } + + fn generate_x25519_key( + &self, + _rng: &mut dyn Rng, + _params: &[keymint::KeyParam], + ) -> Result { + unimpl!(); + } + + fn nist_public_key(&self, _key: &ec::NistKey, _curve: ec::NistCurve) -> Result, Error> { + unimpl!(); + } + + fn ed25519_public_key(&self, _key: &ec::Ed25519Key) -> Result, Error> { + unimpl!(); + } + + fn x25519_public_key(&self, _key: &ec::X25519Key) -> Result, Error> { + unimpl!(); + } + + fn begin_agree( + &self, + _key: OpaqueOr, + ) -> Result, Error> { + unimpl!(); + } + + fn begin_sign( + &self, + _key: OpaqueOr, + _digest: Digest, + ) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`keyblob::SecureDeletionSecretManager`]. +pub struct NoOpSdsManager; +impl keyblob::SecureDeletionSecretManager for NoOpSdsManager { + fn get_or_create_factory_reset_secret( + &mut self, + _rng: &mut dyn Rng, + ) -> Result { + unimpl!(); + } + + fn get_factory_reset_secret(&self) -> Result { + unimpl!(); + } + + fn new_secret( + &mut self, + _rng: &mut dyn Rng, + _purpose: keyblob::SlotPurpose, + ) -> Result<(keyblob::SecureDeletionSlot, keyblob::SecureDeletionData), Error> { + unimpl!(); + } + + fn get_secret( + &self, + _slot: keyblob::SecureDeletionSlot, + ) -> Result { + unimpl!(); + } + fn delete_secret(&mut self, _slot: keyblob::SecureDeletionSlot) -> Result<(), Error> { + unimpl!(); + } + + fn delete_all(&mut self) { + log_unimpl!(); + } +} diff --git a/libs/rust/common/src/keyblob.rs b/libs/rust/common/src/keyblob.rs new file mode 100644 index 0000000..4dd07a7 --- /dev/null +++ b/libs/rust/common/src/keyblob.rs @@ -0,0 +1,474 @@ +// Copyright 2022, 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. + +//! Key blob manipulation functionality. + +use crate::{ + contains_tag_value, crypto, crypto::aes, km_err, tag, try_to_vec, vec_try, Error, + FallibleAllocExt, +}; +use kmr_derive::AsCborValue; +use kmr_wire::keymint::{ + BootInfo, KeyCharacteristics, KeyParam, KeyPurpose, SecurityLevel, VerifiedBootState, +}; +use kmr_wire::{cbor, cbor_type_error, AsCborValue, CborError}; +use log::{error, info}; +use zeroize::ZeroizeOnDrop; + +pub mod legacy; +pub mod sdd_mem; + +#[cfg(test)] +mod tests; + +/// Nonce value of all zeroes used in AES-GCM key encryption. +const ZERO_NONCE: [u8; 12] = [0u8; 12]; + +/// Identifier for secure deletion secret storage slot. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, AsCborValue)] +pub struct SecureDeletionSlot(pub u32); + +/// Keyblob format version. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, AsCborValue)] +pub enum Version { + /// Version 1. + V1 = 0, +} + +/// Encrypted key material, as translated to/from CBOR. +#[derive(Clone, Debug)] +pub enum EncryptedKeyBlob { + /// Version 1 key blob. + V1(EncryptedKeyBlobV1), + // Future versions go here... +} + +impl EncryptedKeyBlob { + /// Construct from serialized data, mapping failure to `ErrorCode::InvalidKeyBlob`. + pub fn new(data: &[u8]) -> Result { + Self::from_slice(data) + .map_err(|e| km_err!(InvalidKeyBlob, "failed to parse keyblob: {:?}", e)) + } + /// Return the secure deletion slot for the key, if present. + pub fn secure_deletion_slot(&self) -> Option { + match self { + EncryptedKeyBlob::V1(blob) => blob.secure_deletion_slot, + } + } + /// Return the additional KEK context for the key. + pub fn kek_context(&self) -> &[u8] { + match self { + EncryptedKeyBlob::V1(blob) => &blob.kek_context, + } + } +} + +impl AsCborValue for EncryptedKeyBlob { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let mut a = match value { + cbor::value::Value::Array(a) if a.len() == 2 => a, + _ => return cbor_type_error(&value, "arr len 2"), + }; + let inner = a.remove(1); + let version = Version::from_cbor_value(a.remove(0))?; + match version { + Version::V1 => Ok(Self::V1(EncryptedKeyBlobV1::from_cbor_value(inner)?)), + } + } + fn to_cbor_value(self) -> Result { + Ok(match self { + EncryptedKeyBlob::V1(inner) => cbor::value::Value::Array( + vec_try![Version::V1.to_cbor_value()?, inner.to_cbor_value()?] + .map_err(|_e: Error| CborError::AllocationFailed)?, + ), + }) + } + fn cddl_typename() -> Option { + Some("EncryptedKeyBlob".to_string()) + } + fn cddl_schema() -> Option { + Some(format!( + "&( + [{}, {}] ; Version::V1 +)", + Version::V1 as i32, + EncryptedKeyBlobV1::cddl_ref() + )) + } + + fn into_vec(self) -> Result, CborError> { + Ok(coset::CborSerializable::to_vec(self.to_cbor_value()?)?) + } +} + +/// Encrypted key material, as translated to/from CBOR. +#[derive(Clone, Debug, AsCborValue)] +pub struct EncryptedKeyBlobV1 { + /// Characteristics associated with the key. + pub characteristics: Vec, + /// Nonce used for the key derivation. + pub key_derivation_input: [u8; 32], + /// Opaque context data needed for root KEK retrieval. + pub kek_context: Vec, + /// Key material encrypted with AES-GCM with: + /// - key produced by [`derive_kek`] + /// - plaintext is the CBOR-serialization of [`crypto::KeyMaterial`] + /// - nonce is all zeroes + /// - no additional data. + pub encrypted_key_material: coset::CoseEncrypt0, + /// Identifier for a slot in secure storage that holds additional secret values + /// that are required to derive the key encryption key. + pub secure_deletion_slot: Option, +} + +/// Trait to handle keyblobs in a format from a previous implementation. +pub trait LegacyKeyHandler { + /// Indicate whether a keyblob is a legacy key format. + fn is_legacy_key(&self, keyblob: &[u8], params: &[KeyParam], root_of_trust: &BootInfo) -> bool { + // The `convert_legacy_key` method includes a security level parameter so that a new + // keyblob can be emitted with the key characterstics assigned appropriately. However, + // for this method the new keyblob is thrown away, so just use `TrustedEnvironment`. + match self.convert_legacy_key( + keyblob, + params, + root_of_trust, + SecurityLevel::TrustedEnvironment, + ) { + Ok(_blob) => { + // Successfully converted the keyblob into current format, so assume that means + // that the keyblob was indeed in the legacy format. + true + } + Err(e) => { + info!("legacy keyblob conversion attempt failed: {:?}", e); + false + } + } + } + + /// Convert a potentially-legacy key into current format. Note that any secure deletion data + /// associated with the old keyblob should not be deleted until a subsequent call to + /// `delete_legacy_key` arrives. + fn convert_legacy_key( + &self, + keyblob: &[u8], + params: &[KeyParam], + root_of_trust: &BootInfo, + sec_level: SecurityLevel, + ) -> Result; + + /// Delete a potentially-legacy keyblob. + fn delete_legacy_key(&mut self, keyblob: &[u8]) -> Result<(), Error>; +} + +/// Secret data that can be mixed into the key derivation inputs for keys; if the secret data is +/// lost, the key is effectively deleted because the key encryption key for the keyblob cannot be +/// re-derived. +#[derive(Clone, PartialEq, Eq, AsCborValue, ZeroizeOnDrop)] +pub struct SecureDeletionData { + /// Secret value that is wiped on factory reset. This should be populated for all keys, to + /// ensure that a factory reset invalidates all keys. + pub factory_reset_secret: [u8; 32], + /// Per-key secret value that is wiped on deletion of a specific key. This is only populated + /// for keys with secure deletion support; for other keys this field will be all zeroes. + pub secure_deletion_secret: [u8; 16], +} + +/// Indication of what kind of key operation requires a secure deletion slot. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum SlotPurpose { + /// Secure deletion slot needed for key generation. + KeyGeneration, + /// Secure deletion slot needed for key import. + KeyImport, + /// Secure deletion slot needed for upgrade of an existing key. + KeyUpgrade, +} + +/// Manager for the mapping between secure deletion slots and the corresponding +/// [`SecureDeletionData`] instances. +pub trait SecureDeletionSecretManager { + /// Return a [`SecureDeletionData`] that has the `factory_reset_secret` populated but which has + /// all zeroes for the `secure_deletion_secret`. If a factory reset secret has not yet been + /// created, do so (possibly using `rng`) + fn get_or_create_factory_reset_secret( + &mut self, + rng: &mut dyn crypto::Rng, + ) -> Result; + + /// Return a [`SecureDeletionData`] that has the `factory_reset_secret` populated + /// but which has all zeroes for the `secure_deletion_secret`. + fn get_factory_reset_secret(&self) -> Result; + + /// Find an empty slot, populate it with a fresh [`SecureDeletionData`] that includes a per-key + /// secret, and return the slot. If the purpose is `SlotPurpose::KeyUpgrade`, there will be a + /// subsequent call to `delete_secret()` for the slot associated with the original keyblob; + /// implementations should reserve additional expansion space to allow for this. + fn new_secret( + &mut self, + rng: &mut dyn crypto::Rng, + purpose: SlotPurpose, + ) -> Result<(SecureDeletionSlot, SecureDeletionData), Error>; + + /// Retrieve a [`SecureDeletionData`] identified by `slot`. + fn get_secret(&self, slot: SecureDeletionSlot) -> Result; + + /// Delete the [`SecureDeletionData`] identified by `slot`. + fn delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), Error>; + + /// Delete all secure deletion data, including the factory reset secret. + fn delete_all(&mut self); +} + +/// RAII class to hold a secure deletion slot. The slot is deleted when the holder is dropped. +struct SlotHolder<'a> { + mgr: &'a mut dyn SecureDeletionSecretManager, + // Invariant: `slot` is non-`None` except on destruction. + slot: Option, +} + +impl Drop for SlotHolder<'_> { + fn drop(&mut self) { + if let Some(slot) = self.slot.take() { + if let Err(e) = self.mgr.delete_secret(slot) { + error!("Failed to delete recently-acquired SDD slot {:?}: {:?}", slot, e); + } + } + } +} + +impl<'a> SlotHolder<'a> { + /// Reserve a new secure deletion slot. + fn new( + mgr: &'a mut dyn SecureDeletionSecretManager, + rng: &mut dyn crypto::Rng, + purpose: SlotPurpose, + ) -> Result<(Self, SecureDeletionData), Error> { + let (slot, sdd) = mgr.new_secret(rng, purpose)?; + Ok((Self { mgr, slot: Some(slot) }, sdd)) + } + + /// Acquire ownership of the secure deletion slot. + fn consume(mut self) -> SecureDeletionSlot { + self.slot.take().unwrap() // Safe: `is_some()` invariant + } +} + +/// Root of trust information for binding into keyblobs. +#[derive(Debug, Clone, AsCborValue)] +pub struct RootOfTrustInfo { + /// Verified boot key. + pub verified_boot_key: Vec, + /// Whether the bootloader is locked. + pub device_boot_locked: bool, + /// State of verified boot for the device. + pub verified_boot_state: VerifiedBootState, +} + +/// Derive a key encryption key used for key blob encryption. The key is an AES-256 key derived +/// from `root_key` using HKDF (RFC 5869) with HMAC-SHA256: +/// - input keying material = a root key held in hardware. If it contains explicit key material, +/// perform full HKDF. If the root key is an opaque one, we assume that +/// the key is able to be directly used on the HKDF expand step. +/// - salt = absent +/// - info = the following three or four chunks of context data concatenated: +/// - content of `key_derivation_input` (which is random data) +/// - CBOR-serialization of `characteristics` +/// - CBOR-serialized array of additional `KeyParam` items in `hidden` +/// - (if `sdd` provided) CBOR serialization of the `SecureDeletionData` +pub fn derive_kek( + kdf: &dyn crypto::Hkdf, + root_key: &crypto::OpaqueOr, + key_derivation_input: &[u8; 32], + characteristics: Vec, + hidden: Vec, + sdd: Option, +) -> Result, Error> { + let mut info = try_to_vec(key_derivation_input)?; + info.try_extend_from_slice(&characteristics.into_vec()?)?; + info.try_extend_from_slice(&hidden.into_vec()?)?; + if let Some(sdd) = sdd { + info.try_extend_from_slice(&sdd.into_vec()?)?; + } + + match root_key { + crypto::OpaqueOr::Explicit(key_material) => { + kdf.hkdf_aes(&[], &key_material.0, &info, aes::Variant::Aes256) + } + key @ crypto::OpaqueOr::Opaque(_) => kdf.expand_aes(key, &info, aes::Variant::Aes256), + } +} + +/// Plaintext key blob. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PlaintextKeyBlob { + /// Characteristics associated with the key. + pub characteristics: Vec, + /// Key Material + pub key_material: crypto::KeyMaterial, +} + +impl PlaintextKeyBlob { + /// Return the set of key parameters at the provided security level. + pub fn characteristics_at(&self, sec_level: SecurityLevel) -> Result<&[KeyParam], Error> { + tag::characteristics_at(&self.characteristics, sec_level) + } + + /// Check that the key is suitable for the given purpose. + pub fn suitable_for(&self, purpose: KeyPurpose, sec_level: SecurityLevel) -> Result<(), Error> { + if contains_tag_value!(self.characteristics_at(sec_level)?, Purpose, purpose) { + Ok(()) + } else { + Err(km_err!(IncompatiblePurpose, "purpose {:?} not supported by keyblob", purpose)) + } + } +} + +/// Consume a plaintext keyblob and emit an encrypted version. If `sdd_mgr` is provided, +/// a secure deletion slot will be embedded into the keyblob. +#[allow(clippy::too_many_arguments)] +pub fn encrypt( + sec_level: SecurityLevel, + sdd_mgr: Option<&mut dyn SecureDeletionSecretManager>, + aes: &dyn crypto::Aes, + kdf: &dyn crypto::Hkdf, + rng: &mut dyn crypto::Rng, + root_key: &crypto::OpaqueOr, + kek_context: &[u8], + plaintext_keyblob: PlaintextKeyBlob, + hidden: Vec, + purpose: SlotPurpose, +) -> Result { + // Determine if secure deletion is required by examining the key characteristics at our + // security level. + let requires_sdd = plaintext_keyblob + .characteristics_at(sec_level)? + .iter() + .any(|param| matches!(param, KeyParam::RollbackResistance | KeyParam::UsageCountLimit(1))); + let (slot_holder, sdd) = match (requires_sdd, sdd_mgr) { + (true, Some(sdd_mgr)) => { + // Reserve a slot and store it in a [`SlotHolder`] so that it will definitely be + // released if there are any errors encountered below. + let (holder, sdd) = SlotHolder::new(sdd_mgr, rng, purpose)?; + (Some(holder), Some(sdd)) + } + (true, None) => { + return Err(km_err!( + RollbackResistanceUnavailable, + "no secure secret storage available" + )) + } + (false, Some(sdd_mgr)) => { + // Create a secure deletion secret that just has the factory reset secret in it. + (None, Some(sdd_mgr.get_or_create_factory_reset_secret(rng)?)) + } + (false, None) => { + // No secure storage available, and none explicitly asked for. However, this keyblob + // will survive factory reset. + (None, None) + } + }; + let characteristics = plaintext_keyblob.characteristics; + let mut key_derivation_input = [0u8; 32]; + rng.fill_bytes(&mut key_derivation_input[..]); + let kek = + derive_kek(kdf, root_key, &key_derivation_input, characteristics.clone(), hidden, sdd)?; + + // Encrypt the plaintext key material into a `Cose_Encrypt0` structure. + let cose_encrypt = coset::CoseEncrypt0Builder::new() + .protected(coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build()) + .try_create_ciphertext::<_, Error>( + &plaintext_keyblob.key_material.into_vec()?, + &[], + move |pt, aad| { + let mut op = aes.begin_aead( + kek, + crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE }, + crypto::SymmetricOperation::Encrypt, + )?; + op.update_aad(aad)?; + let mut ct = op.update(pt)?; + ct.try_extend_from_slice(&op.finish()?)?; + Ok(ct) + }, + )? + .build(); + + Ok(EncryptedKeyBlob::V1(EncryptedKeyBlobV1 { + characteristics, + key_derivation_input, + kek_context: try_to_vec(kek_context)?, + encrypted_key_material: cose_encrypt, + secure_deletion_slot: slot_holder.map(|h| h.consume()), + })) +} + +/// Consume an encrypted keyblob and emit an decrypted version. +pub fn decrypt( + sdd_mgr: Option<&dyn SecureDeletionSecretManager>, + aes: &dyn crypto::Aes, + kdf: &dyn crypto::Hkdf, + root_key: &crypto::OpaqueOr, + encrypted_keyblob: EncryptedKeyBlob, + hidden: Vec, +) -> Result { + let EncryptedKeyBlob::V1(encrypted_keyblob) = encrypted_keyblob; + let sdd = match (encrypted_keyblob.secure_deletion_slot, sdd_mgr) { + (Some(slot), Some(sdd_mgr)) => Some(sdd_mgr.get_secret(slot)?), + (Some(_slot), None) => { + return Err(km_err!( + InvalidKeyBlob, + "keyblob has sdd slot but no secure storage available" + )) + } + (None, Some(sdd_mgr)) => { + // Keyblob should be bound to (just) the factory reset secret. + Some(sdd_mgr.get_factory_reset_secret()?) + } + (None, None) => None, + }; + let characteristics = encrypted_keyblob.characteristics; + let kek = derive_kek( + kdf, + root_key, + &encrypted_keyblob.key_derivation_input, + characteristics.clone(), + hidden, + sdd, + )?; + let cose_encrypt = encrypted_keyblob.encrypted_key_material; + + let extended_aad = coset::enc_structure_data( + coset::EncryptionContext::CoseEncrypt0, + cose_encrypt.protected.clone(), + &[], // no external AAD + ); + + let mut op = aes.begin_aead( + kek, + crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE }, + crypto::SymmetricOperation::Decrypt, + )?; + op.update_aad(&extended_aad)?; + let mut pt_data = op.update(&cose_encrypt.ciphertext.unwrap_or_default())?; + pt_data.try_extend_from_slice( + &op.finish().map_err(|e| km_err!(InvalidKeyBlob, "failed to decrypt keyblob: {:?}", e))?, + )?; + + Ok(PlaintextKeyBlob { + characteristics, + key_material: ::from_slice(&pt_data)?, + }) +} diff --git a/libs/rust/common/src/keyblob/keyblob.cddl b/libs/rust/common/src/keyblob/keyblob.cddl new file mode 100644 index 0000000..1e1ea6f --- /dev/null +++ b/libs/rust/common/src/keyblob/keyblob.cddl @@ -0,0 +1,274 @@ +; encrypted_key_material is AES-GCM encrypted with: +; - key derived as described below +; - plaintext is the CBOR-serialization of `KeyMaterial` +; - nonce value is fixed, all zeroes +; - no additional data +; +; Key derivation uses HKDF (RFC 5869) with HMAC-SHA256 to generate an AES-256 key: +; - input keying material = a root key held in hardware +; - salt = absent +; - info = the following three or four chunks of context data concatenated: +; - content of `EncryptedKeyBlob.key_derivation_input` (a random nonce) +; - CBOR-serialization of `EncryptedKeyBlob.characteristics` +; - CBOR-serialized array of additional hidden `KeyParam` items associated with the key, specifically: +; - [Tag_ApplicationId, bstr] if required +; - [Tag_ApplicationData, bstr] if required +; - [Tag_RootOfTrust, bstr .cbor RootOfTrustInfo] +; - (if secure storage is available) CBOR serialization of the `SecureDeletionData` structure, with: +; - `factory_reset_secret` always populated +; - `secure_deletion_secret` populated with: +; - all zeroes (if `EncryptedKeyBlob.secure_deletion_slot` is empty) +; - the contents of the slot (if `EncryptedKeyBlob.secure_deletion_slot` is non-empty) +EncryptedKeyBlob = &( + [0, EncryptedKeyBlobV1] ; Version::V1 +) +Version = &( + Version_V1: 0, +) +EncryptedKeyBlobV1 = [ + characteristics: [* KeyCharacteristics], + key_derivation_input: bstr .size 32, + kek_context: bstr, + encrypted_key_material: #6.16(Cose_Encrypt0), + secure_deletion_slot: [? SecureDeletionSlot], +] +KeyCharacteristics = [ + security_level: SecurityLevel, + authorizations: [* KeyParam], +] +Cose_Encrypt0 = [ protected: bstr, unprotected: { * (int / tstr) => any }, ciphertext: bstr / nil ] +KeyMaterial = &( + ; For each variant the `bool` second entry indicates whether the bstr for the key material + ; is opaque (true), or explicit (false). + [32, bool, bstr], ; Algorithm_Aes + [33, bool, bstr], ; Algorithm_TripleDes + [128, bool, bstr], ; Algorithm_Hmac + ; An explicit RSA key is in the form of an ASN.1 DER encoding of a PKCS#1 `RSAPrivateKey` + ; structure, as specified by RFC 3447 sections A.1.2 and 3.2. + [1, bool, bstr], ; Algorithm_Rsa + ; An explicit EC key for a NIST curve is in the form of an ASN.1 DER encoding of a + ; `ECPrivateKey` structure, as specified by RFC 5915 section 3. + ; An explicit EC key for curve 25519 is the raw key bytes. + [3, bool, [EcCurve, CurveType, bstr]], ; Algorithm_Ec +) +SecureDeletionSlot = int +SecureDeletionData = [ + factory_reset_secret: bstr .size 32, + secure_deletion_secret: bstr .size 16, +] +RootOfTrustInfo = [ + verified_boot_key: bstr, + device_boot_locked: bool, + verified_boot_state: VerifiedBootState, +] +VerifiedBootState = &( + VerifiedBootState_Verified: 0, + VerifiedBootState_SelfSigned: 1, + VerifiedBootState_Unverified: 2, + VerifiedBootState_Failed: 3, +) +SecurityLevel = &( + SecurityLevel_Software: 0, + SecurityLevel_TrustedEnvironment: 1, + SecurityLevel_Strongbox: 2, + SecurityLevel_Keystore: 100, +) +KeyParam = &( + [268435458, Algorithm], ; Tag_Algorithm + [536870916, BlockMode], ; Tag_BlockMode + [536870918, PaddingMode], ; Tag_Padding + [536870917, Digest], ; Tag_Digest + [268435466, EcCurve], ; Tag_EcCurve + [268436158, KeyOrigin], ; Tag_Origin + [536870913, KeyPurpose], ; Tag_Purpose + [805306371, KeySizeInBits], ; Tag_KeySize + [1879048199, bstr], ; Tag_CallerNonce + [805306376, int], ; Tag_MinMacLength + [1342177480, RsaExponent], ; Tag_RsaPublicExponent + [1879048394, true], ; Tag_IncludeUniqueId + [536871115, Digest], ; Tag_RsaOaepMgfDigest + [1879048494, true], ; Tag_BootloaderOnly + [1879048495, true], ; Tag_RollbackResistance + [1879048497, true], ; Tag_EarlyBootOnly + [1610613136, DateTime], ; Tag_ActiveDatetime + [1610613137, DateTime], ; Tag_OriginationExpireDatetime + [1610613138, DateTime], ; Tag_UsageExpireDatetime + [805306772, int], ; Tag_MaxUsesPerBoot + [805306773, int], ; Tag_UsageCountLimit + [805306869, int], ; Tag_UserId + [-1610612234, int], ; Tag_UserSecureId + [1879048695, true], ; Tag_NoAuthRequired + [268435960, int], ; Tag_UserAuthType + [805306873, int], ; Tag_AuthTimeout + [1879048698, true], ; Tag_AllowWhileOnBody + [1879048699, true], ; Tag_TrustedUserPresenceRequired + [1879048700, true], ; Tag_TrustedConfirmationRequired + [1879048701, true], ; Tag_UnlockedDeviceRequired + [-1879047591, bstr], ; Tag_ApplicationId + [-1879047492, bstr], ; Tag_ApplicationData + [1610613437, DateTime], ; Tag_CreationDatetime + [-1879047488, bstr], ; Tag_RootOfTrust + [805307073, int], ; Tag_OsVersion + [805307074, int], ; Tag_OsPatchlevel + [-1879047484, bstr], ; Tag_AttestationChallenge + [-1879047483, bstr], ; Tag_AttestationApplicationId + [-1879047482, bstr], ; Tag_AttestationIdBrand + [-1879047481, bstr], ; Tag_AttestationIdDevice + [-1879047480, bstr], ; Tag_AttestationIdProduct + [-1879047479, bstr], ; Tag_AttestationIdSerial + [-1879047478, bstr], ; Tag_AttestationIdImei + [-1879047469, bstr], ; Tag_AttestationIdSecondImei + [-1879047477, bstr], ; Tag_AttestationIdMeid + [-1879047476, bstr], ; Tag_AttestationIdManufacturer + [-1879047475, bstr], ; Tag_AttestationIdModel + [805307086, int], ; Tag_VendorPatchlevel + [805307087, int], ; Tag_BootPatchlevel + [1879048912, true], ; Tag_DeviceUniqueAttestation + [1879048914, true], ; Tag_StorageKey + [-1879047191, bstr], ; Tag_Nonce + [805307371, int], ; Tag_MacLength + [1879049196, true], ; Tag_ResetSinceIdRotation + [-2147482642, bstr], ; Tag_CertificateSerial + [-1879047185, bstr], ; Tag_CertificateSubject + [1610613744, DateTime], ; Tag_CertificateNotBefore + [1610613745, DateTime], ; Tag_CertificateNotAfter + [805307378, int], ; Tag_MaxBootLevel + [-1879047468, bstr], ; Tag_ModuleHash +) +Tag = &( + Tag_Invalid: 0, + Tag_Purpose: 536870913, + Tag_Algorithm: 268435458, + Tag_KeySize: 805306371, + Tag_BlockMode: 536870916, + Tag_Digest: 536870917, + Tag_Padding: 536870918, + Tag_CallerNonce: 1879048199, + Tag_MinMacLength: 805306376, + Tag_EcCurve: 268435466, + Tag_RsaPublicExponent: 1342177480, + Tag_IncludeUniqueId: 1879048394, + Tag_RsaOaepMgfDigest: 536871115, + Tag_BootloaderOnly: 1879048494, + Tag_RollbackResistance: 1879048495, + Tag_HardwareType: 268435760, + Tag_EarlyBootOnly: 1879048497, + Tag_ActiveDatetime: 1610613136, + Tag_OriginationExpireDatetime: 1610613137, + Tag_UsageExpireDatetime: 1610613138, + Tag_MinSecondsBetweenOps: 805306771, + Tag_MaxUsesPerBoot: 805306772, + Tag_UsageCountLimit: 805306773, + Tag_UserId: 805306869, + Tag_UserSecureId: -1610612234, + Tag_NoAuthRequired: 1879048695, + Tag_UserAuthType: 268435960, + Tag_AuthTimeout: 805306873, + Tag_AllowWhileOnBody: 1879048698, + Tag_TrustedUserPresenceRequired: 1879048699, + Tag_TrustedConfirmationRequired: 1879048700, + Tag_UnlockedDeviceRequired: 1879048701, + Tag_ApplicationId: -1879047591, + Tag_ApplicationData: -1879047492, + Tag_CreationDatetime: 1610613437, + Tag_Origin: 268436158, + Tag_RootOfTrust: -1879047488, + Tag_OsVersion: 805307073, + Tag_OsPatchlevel: 805307074, + Tag_UniqueId: -1879047485, + Tag_AttestationChallenge: -1879047484, + Tag_AttestationApplicationId: -1879047483, + Tag_AttestationIdBrand: -1879047482, + Tag_AttestationIdDevice: -1879047481, + Tag_AttestationIdProduct: -1879047480, + Tag_AttestationIdSerial: -1879047479, + Tag_AttestationIdImei: -1879047478, + Tag_AttestationIdMeid: -1879047477, + Tag_AttestationIdManufacturer: -1879047476, + Tag_AttestationIdModel: -1879047475, + Tag_VendorPatchlevel: 805307086, + Tag_BootPatchlevel: 805307087, + Tag_DeviceUniqueAttestation: 1879048912, + Tag_IdentityCredentialKey: 1879048913, + Tag_StorageKey: 1879048914, + Tag_AttestationIdSecondImei: -1879047469, + Tag_AssociatedData: -1879047192, + Tag_Nonce: -1879047191, + Tag_MacLength: 805307371, + Tag_ResetSinceIdRotation: 1879049196, + Tag_ConfirmationToken: -1879047187, + Tag_CertificateSerial: -2147482642, + Tag_CertificateSubject: -1879047185, + Tag_CertificateNotBefore: 1610613744, + Tag_CertificateNotAfter: 1610613745, + Tag_MaxBootLevel: 805307378, + Tag_ModuleHash: -1879047468, +) +Algorithm = &( + Algorithm_Rsa: 1, + Algorithm_Ec: 3, + Algorithm_Aes: 32, + Algorithm_TripleDes: 33, + Algorithm_Hmac: 128, +) +BlockMode = &( + BlockMode_Ecb: 1, + BlockMode_Cbc: 2, + BlockMode_Ctr: 3, + BlockMode_Gcm: 32, +) +Digest = &( + Digest_None: 0, + Digest_Md5: 1, + Digest_Sha1: 2, + Digest_Sha224: 3, + Digest_Sha256: 4, + Digest_Sha384: 5, + Digest_Sha512: 6, +) +EcCurve = &( + EcCurve_P224: 0, + EcCurve_P256: 1, + EcCurve_P384: 2, + EcCurve_P521: 3, + EcCurve_Curve25519: 4, +) +CurveType = &( + CurveType_Nist: 0, + CurveType_EdDsa: 1, + CurveType_Xdh: 2, +) +KeyOrigin = &( + KeyOrigin_Generated: 0, + KeyOrigin_Derived: 1, + KeyOrigin_Imported: 2, + KeyOrigin_Reserved: 3, + KeyOrigin_SecurelyImported: 4, +) +KeyPurpose = &( + KeyPurpose_Encrypt: 0, + KeyPurpose_Decrypt: 1, + KeyPurpose_Sign: 2, + KeyPurpose_Verify: 3, + KeyPurpose_WrapKey: 5, + KeyPurpose_AgreeKey: 6, + KeyPurpose_AttestKey: 7, +) +HardwareAuthenticatorType = &( + HardwareAuthenticatorType_None: 0, + HardwareAuthenticatorType_Password: 1, + HardwareAuthenticatorType_Fingerprint: 2, + HardwareAuthenticatorType_Any: -1, +) +PaddingMode = &( + PaddingMode_None: 1, + PaddingMode_RsaOaep: 2, + PaddingMode_RsaPss: 3, + PaddingMode_RsaPkcs115Encrypt: 4, + PaddingMode_RsaPkcs115Sign: 5, + PaddingMode_Pkcs7: 64, +) +DateTime = int +KeySizeInBits = int +RsaExponent = int + diff --git a/libs/rust/common/src/keyblob/legacy.rs b/libs/rust/common/src/keyblob/legacy.rs new file mode 100644 index 0000000..1df5a36 --- /dev/null +++ b/libs/rust/common/src/keyblob/legacy.rs @@ -0,0 +1,293 @@ +// Copyright 2022, 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. + +//! Utilities for handling legacy KeyMaster/KeyMint key blobs. + +use crate::tag::legacy::{consume_i32, consume_u32, consume_u8, consume_vec}; +use crate::{ + crypto, get_opt_tag_value, km_err, try_to_vec, vec_try_with_capacity, Error, FallibleAllocExt, +}; +use core::mem::size_of; +use kmr_wire::keymint::KeyParam; + +#[cfg(test)] +mod tests; + +/// Key blob version. +const KEY_BLOB_VERSION: u8 = 0; + +/// Hard-coded HMAC key used for keyblob authentication. +const HMAC_KEY: &[u8] = b"IntegrityAssuredBlob0\0"; + +/// Format of encrypted key blob. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AuthEncryptedBlobFormat { + /// AES-OCB + AesOcb = 0, + /// AES-GCM encryption. + AesGcmWithSwEnforced = 1, + /// AES-GCM encryption including secure deletion secret. + AesGcmWithSecureDeletion = 2, + /// Versioned AES-GCM encryption. + AesGcmWithSwEnforcedVersioned = 3, + /// Versioned AES-GCM encryption including secure deletion secret. + AesGcmWithSecureDeletionVersioned = 4, +} + +impl AuthEncryptedBlobFormat { + /// Indicate whether this format requires secure deletion support. + pub fn requires_secure_deletion(&self) -> bool { + matches!(self, Self::AesGcmWithSecureDeletion | Self::AesGcmWithSecureDeletionVersioned) + } + /// Indicate whether this format is versioned. + pub fn is_versioned(&self) -> bool { + matches!( + self, + Self::AesGcmWithSwEnforcedVersioned | Self::AesGcmWithSecureDeletionVersioned + ) + } +} + +/// Encrypted key blob, including key characteristics. +#[derive(Debug, PartialEq, Eq)] +pub struct EncryptedKeyBlob { + /// Format of the keyblob. + pub format: AuthEncryptedBlobFormat, + /// IV for encryption. + pub nonce: Vec, + /// Encrypted key material. + pub ciphertext: Vec, + /// Authenticated encryption tag. + pub tag: Vec, + + // The following two fields are preset iff `format.is_versioned()` + /// KDF version for the key. + pub kdf_version: Option, + /// Additional information for key derivation. + pub addl_info: Option, + + /// Hardware-enforced key characteristics. + pub hw_enforced: Vec, + /// Software-enforced key characteristics. + pub sw_enforced: Vec, + /// Secure deletion key slot. + pub key_slot: Option, +} + +impl EncryptedKeyBlob { + /// Serialize an [`EncryptedKeyBlob`]. + pub fn serialize(&self) -> Result, Error> { + let hw_enforced_data = crate::tag::legacy::serialize(&self.hw_enforced)?; + let sw_enforced_data = crate::tag::legacy::serialize(&self.sw_enforced)?; + let mut result = vec_try_with_capacity!( + size_of::() + + size_of::() + + self.nonce.len() + + size_of::() + + self.ciphertext.len() + + size_of::() + + self.tag.len() + + hw_enforced_data.len() + + sw_enforced_data.len() + + size_of::() + )?; + result.push(self.format as u8); + result.extend_from_slice(&(self.nonce.len() as u32).to_ne_bytes()); + result.extend_from_slice(&self.nonce); + result.extend_from_slice(&(self.ciphertext.len() as u32).to_ne_bytes()); + result.extend_from_slice(&self.ciphertext); + result.extend_from_slice(&(self.tag.len() as u32).to_ne_bytes()); + result.extend_from_slice(&self.tag); + if self.format.is_versioned() { + let kdf_version = self.kdf_version.ok_or_else(|| { + km_err!(InvalidKeyBlob, "keyblob of format {:?} missing kdf_version", self.format) + })?; + let addl_info = self.addl_info.ok_or_else(|| { + km_err!(InvalidKeyBlob, "keyblob of format {:?} missing addl_info", self.format) + })? as u32; + result.extend_from_slice(&kdf_version.to_ne_bytes()); + result.extend_from_slice(&addl_info.to_ne_bytes()); + } + result.extend_from_slice(&hw_enforced_data); + result.extend_from_slice(&sw_enforced_data); + if let Some(slot) = self.key_slot { + result.extend_from_slice(&slot.to_ne_bytes()); + } + Ok(result) + } + + /// Parse a serialized [`KeyBlob`]. + pub fn deserialize(mut data: &[u8]) -> Result { + let format = match consume_u8(&mut data)? { + x if x == AuthEncryptedBlobFormat::AesOcb as u8 => AuthEncryptedBlobFormat::AesOcb, + x if x == AuthEncryptedBlobFormat::AesGcmWithSwEnforced as u8 => { + AuthEncryptedBlobFormat::AesGcmWithSwEnforced + } + x if x == AuthEncryptedBlobFormat::AesGcmWithSecureDeletion as u8 => { + AuthEncryptedBlobFormat::AesGcmWithSecureDeletion + } + x if x == AuthEncryptedBlobFormat::AesGcmWithSwEnforcedVersioned as u8 => { + AuthEncryptedBlobFormat::AesGcmWithSwEnforcedVersioned + } + x if x == AuthEncryptedBlobFormat::AesGcmWithSecureDeletionVersioned as u8 => { + AuthEncryptedBlobFormat::AesGcmWithSecureDeletionVersioned + } + x => return Err(km_err!(InvalidKeyBlob, "unexpected blob format {}", x)), + }; + + let nonce = consume_vec(&mut data)?; + let ciphertext = consume_vec(&mut data)?; + let tag = consume_vec(&mut data)?; + let mut kdf_version = None; + let mut addl_info = None; + if format.is_versioned() { + kdf_version = Some(consume_u32(&mut data)?); + addl_info = Some(consume_i32(&mut data)?); + } + let hw_enforced = crate::tag::legacy::deserialize(&mut data)?; + let sw_enforced = crate::tag::legacy::deserialize(&mut data)?; + + let key_slot = match data.len() { + 0 => None, + 4 => Some(consume_u32(&mut data)?), + _ => return Err(km_err!(InvalidKeyBlob, "unexpected remaining length {}", data.len())), + }; + + Ok(EncryptedKeyBlob { + format, + nonce, + ciphertext, + tag, + kdf_version, + addl_info, + hw_enforced, + sw_enforced, + key_slot, + }) + } +} + +/// Plaintext key blob, with key characteristics. +#[derive(Debug, PartialEq, Eq)] +pub struct KeyBlob { + /// Raw key material. + pub key_material: Vec, + /// Hardware-enforced key characteristics. + pub hw_enforced: Vec, + /// Software-enforced key characteristics. + pub sw_enforced: Vec, +} + +impl KeyBlob { + /// Size (in bytes) of appended MAC. + pub const MAC_LEN: usize = 8; + + /// Serialize a [`KeyBlob`]. + pub fn serialize( + &self, + hmac: &H, + hidden: &[KeyParam], + ) -> Result, crate::Error> { + let hw_enforced_data = crate::tag::legacy::serialize(&self.hw_enforced)?; + let sw_enforced_data = crate::tag::legacy::serialize(&self.sw_enforced)?; + let mut result = vec_try_with_capacity!( + size_of::() + + size_of::() + + self.key_material.len() + + hw_enforced_data.len() + + sw_enforced_data.len() + )?; + result.push(KEY_BLOB_VERSION); + result.extend_from_slice(&(self.key_material.len() as u32).to_ne_bytes()); + result.extend_from_slice(&self.key_material); + result.extend_from_slice(&hw_enforced_data); + result.extend_from_slice(&sw_enforced_data); + let mac = Self::compute_hmac(hmac, &result, hidden)?; + result.extend_from_slice(&mac); + Ok(result) + } + + /// Parse a serialized [`KeyBlob`]. + pub fn deserialize( + hmac: &H, + mut data: &[u8], + hidden: &[KeyParam], + comparator: E, + ) -> Result { + if data.len() < (Self::MAC_LEN + 4 + 4 + 4) { + return Err(km_err!(InvalidKeyBlob, "blob not long enough (len = {})", data.len())); + } + + // Check the HMAC in the last 8 bytes before doing anything else. + let mac = &data[data.len() - Self::MAC_LEN..]; + let computed_mac = Self::compute_hmac(hmac, &data[..data.len() - Self::MAC_LEN], hidden)?; + if comparator.ne(mac, &computed_mac) { + return Err(km_err!(InvalidKeyBlob, "invalid key blob")); + } + + let version = consume_u8(&mut data)?; + if version != KEY_BLOB_VERSION { + return Err(km_err!(InvalidKeyBlob, "unexpected blob version {}", version)); + } + let key_material = consume_vec(&mut data)?; + let hw_enforced = crate::tag::legacy::deserialize(&mut data)?; + let sw_enforced = crate::tag::legacy::deserialize(&mut data)?; + + // Should just be the (already-checked) MAC left. + let rest = &data[Self::MAC_LEN..]; + if !rest.is_empty() { + return Err(km_err!(InvalidKeyBlob, "extra data (len {})", rest.len())); + } + Ok(KeyBlob { key_material, hw_enforced, sw_enforced }) + } + + /// Compute the authentication HMAC for a KeyBlob. This is built as: + /// HMAC-SHA256(HK, data || serialize(hidden)) + /// with HK = b"IntegrityAssuredBlob0\0". + pub fn compute_hmac( + hmac: &H, + data: &[u8], + hidden: &[KeyParam], + ) -> Result, crate::Error> { + let hidden_data = crate::tag::legacy::serialize(hidden)?; + let mut op = hmac.begin( + crypto::hmac::Key(try_to_vec(HMAC_KEY)?).into(), + kmr_wire::keymint::Digest::Sha256, + )?; + op.update(data)?; + op.update(&hidden_data)?; + let mut tag = op.finish()?; + tag.truncate(Self::MAC_LEN); + Ok(tag) + } +} + +/// Build the parameters that are used as the hidden input to HMAC calculations: +/// - `ApplicationId(data)` if present +/// - `ApplicationData(data)` if present +/// - (repeated) `RootOfTrust(rot)` where `rot` is a hardcoded root of trust (expected to +/// be the CBOR serialization of a `RootOfTrustInfo` instance). +pub fn hidden(params: &[KeyParam], rots: &[&[u8]]) -> Result, Error> { + let mut results = Vec::new(); + if let Ok(Some(app_id)) = get_opt_tag_value!(params, ApplicationId) { + results.try_push(KeyParam::ApplicationId(try_to_vec(app_id)?))?; + } + if let Ok(Some(app_data)) = get_opt_tag_value!(params, ApplicationData) { + results.try_push(KeyParam::ApplicationData(try_to_vec(app_data)?))?; + } + for rot in rots { + results.try_push(KeyParam::RootOfTrust(try_to_vec(rot)?))?; + } + Ok(results) +} diff --git a/libs/rust/common/src/keyblob/legacy/tests.rs b/libs/rust/common/src/keyblob/legacy/tests.rs new file mode 100644 index 0000000..322c959 --- /dev/null +++ b/libs/rust/common/src/keyblob/legacy/tests.rs @@ -0,0 +1,281 @@ +// Copyright 2022, 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. + +use super::*; +use crate::expect_err; +use crate::tag::legacy::{consume_u32, consume_u64, consume_u8, consume_vec}; + +#[test] +fn test_consume_u8() { + let buffer = [1, 2]; + let mut data = &buffer[..]; + assert_eq!(1u8, consume_u8(&mut data).unwrap()); + assert_eq!(2u8, consume_u8(&mut data).unwrap()); + let result = consume_u8(&mut data); + expect_err!(result, "failed to find 1 byte"); +} + +#[test] +fn test_consume_u32() { + let buffer = [ + 0x01, 0x02, 0x03, 0x04, // + 0x04, 0x03, 0x02, 0x01, // + 0x11, 0x12, 0x13, + ]; + let mut data = &buffer[..]; + assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap()); + assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap()); + let result = consume_u32(&mut data); + expect_err!(result, "failed to find 4 bytes"); +} + +#[test] +fn test_consume_u64() { + let buffer = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + ]; + let mut data = &buffer[..]; + assert_eq!(0x0807060504030201u64, consume_u64(&mut data).unwrap()); + assert_eq!(0x0102030405060708u64, consume_u64(&mut data).unwrap()); + let result = consume_u64(&mut data); + expect_err!(result, "failed to find 8 bytes"); +} + +#[test] +fn test_consume_vec() { + let buffer = [ + 0x01, 0x00, 0x00, 0x00, 0xaa, // + 0x00, 0x00, 0x00, 0x00, // + 0x01, 0x00, 0x00, 0x00, 0xbb, // + 0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data + ]; + let mut data = &buffer[..]; + assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap()); + assert_eq!(Vec::::new(), consume_vec(&mut data).unwrap()); + assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap()); + let result = consume_vec(&mut data); + expect_err!(result, "failed to find 7 bytes"); + + let buffer = [ + 0x01, 0x00, 0x00, // + ]; + let mut data = &buffer[..]; + let result = consume_vec(&mut data); + expect_err!(result, "failed to find 4 bytes"); +} + +#[test] +fn test_serialize_encrypted_keyblob() { + let tests = vec![ + ( + concat!( + "00", // format + "01000000", + "aa", // nonce + "02000000", + "bbbb", // ciphertext + "01000000", + "cc", // tag + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + ), + EncryptedKeyBlob { + format: AuthEncryptedBlobFormat::AesOcb, + nonce: vec![0xaa], + ciphertext: vec![0xbb, 0xbb], + tag: vec![0xcc], + kdf_version: None, + addl_info: None, + hw_enforced: vec![], + sw_enforced: vec![], + key_slot: None, + }, + ), + ( + concat!( + "01", // format + "01000000", + "aa", // nonce + "02000000", + "bbbb", // ciphertext + "01000000", + "cc", // tag + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + "06000000", + ), + EncryptedKeyBlob { + format: AuthEncryptedBlobFormat::AesGcmWithSwEnforced, + nonce: vec![0xaa], + ciphertext: vec![0xbb, 0xbb], + tag: vec![0xcc], + kdf_version: None, + addl_info: None, + hw_enforced: vec![], + sw_enforced: vec![], + key_slot: Some(6), + }, + ), + ( + concat!( + "03", // format + "01000000", + "aa", // nonce + "02000000", + "bbbb", // ciphertext + "01000000", + "cc", // tag + "01010101", // kdf_version + "04040404", // addl_info + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + "06000000", + ), + EncryptedKeyBlob { + format: AuthEncryptedBlobFormat::AesGcmWithSwEnforcedVersioned, + nonce: vec![0xaa], + ciphertext: vec![0xbb, 0xbb], + tag: vec![0xcc], + kdf_version: Some(0x01010101), + addl_info: Some(0x04040404), + hw_enforced: vec![], + sw_enforced: vec![], + key_slot: Some(6), + }, + ), + ]; + for (hex_data, want) in tests { + let data = hex::decode(hex_data).unwrap(); + let got = EncryptedKeyBlob::deserialize(&data).unwrap(); + assert_eq!(got, want); + let new_data = got.serialize().unwrap(); + assert_eq!(new_data, data); + } +} + +#[test] +fn test_deserialize_encrypted_keyblob_fail() { + let tests = vec![ + ( + concat!( + "09", // format (invalid) + "01000000", + "aa", // nonce + "02000000", + "bbbb", // ciphertext + "01000000", + "cc", // tag + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + ), + "unexpected blob format 9", + ), + ( + concat!( + "02", // format + "01000000", + "aa", // nonce + "02000000", + "bbbb", // ciphertext + "01000000", + "cc", // tag + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + "060000", + ), + "unexpected remaining length 3", + ), + ]; + for (hex_data, msg) in tests { + let data = hex::decode(hex_data).unwrap(); + let result = EncryptedKeyBlob::deserialize(&data); + expect_err!(result, msg); + } +} + +#[test] +fn test_deserialize_encrypted_keyblob_truncated() { + let data = hex::decode(concat!( + "00", // format + "01000000", + "aa", // nonce + "02000000", + "bbbb", // ciphertext + "01000000", + "cc", // tag + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + )) + .unwrap(); + assert!(EncryptedKeyBlob::deserialize(&data).is_ok()); + for len in 0..data.len() - 1 { + // Any truncation of this data is invalid. + assert!( + EncryptedKeyBlob::deserialize(&data[..len]).is_err(), + "deserialize of data[..{}] subset (len={}) unexpectedly succeeded", + len, + data.len() + ); + } +} diff --git a/libs/rust/common/src/keyblob/sdd_mem.rs b/libs/rust/common/src/keyblob/sdd_mem.rs new file mode 100644 index 0000000..0c3cbea --- /dev/null +++ b/libs/rust/common/src/keyblob/sdd_mem.rs @@ -0,0 +1,103 @@ +// Copyright 2022, 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. + +//! In-memory secure deletion secret manager. +//! +//! Only suitable for development/testing (as secrets are lost on restart). + +use super::{SecureDeletionData, SecureDeletionSecretManager, SecureDeletionSlot, SlotPurpose}; +use crate::{crypto, km_err, Error}; + +/// Secure deletion secret manager that keeps state in memory. Provided as an example only; do not +/// use in a real system (keys will not survive reboot if you do). +pub struct InMemorySlotManager { + factory_secret: Option<[u8; 32]>, + slots: [Option; N], +} + +impl Default for InMemorySlotManager { + fn default() -> Self { + Self { + factory_secret: None, + // Work around Rust limitation that `[None; N]` doesn't work. + slots: [(); N].map(|_| Option::::default()), + } + } +} + +impl SecureDeletionSecretManager for InMemorySlotManager { + fn get_or_create_factory_reset_secret( + &mut self, + rng: &mut dyn crypto::Rng, + ) -> Result { + if self.factory_secret.is_none() { + // No factory reset secret created yet, so do so now. + let mut secret = [0; 32]; + rng.fill_bytes(&mut secret[..]); + self.factory_secret = Some(secret); + } + self.get_factory_reset_secret() + } + + fn get_factory_reset_secret(&self) -> Result { + match self.factory_secret { + Some(secret) => Ok(SecureDeletionData { + factory_reset_secret: secret, + secure_deletion_secret: [0; 16], + }), + None => Err(km_err!(UnknownError, "no factory secret available!")), + } + } + + fn new_secret( + &mut self, + rng: &mut dyn crypto::Rng, + _purpose: SlotPurpose, + ) -> Result<(SecureDeletionSlot, SecureDeletionData), Error> { + for idx in 0..N { + if self.slots[idx].is_none() { + let mut sdd = self.get_or_create_factory_reset_secret(rng)?; + rng.fill_bytes(&mut sdd.secure_deletion_secret[..]); + self.slots[idx] = Some(sdd.clone()); + return Ok((SecureDeletionSlot(idx as u32), sdd)); + } + } + Err(km_err!(RollbackResistanceUnavailable, "full")) + } + + fn get_secret(&self, slot: SecureDeletionSlot) -> Result { + let idx = slot.0 as usize; + if !(0..N).contains(&idx) { + return Err(km_err!(InvalidKeyBlob, "slot idx out of bounds")); + } + match &self.slots[idx] { + Some(data) => Ok(data.clone()), + None => Err(km_err!(InvalidKeyBlob, "slot idx empty")), + } + } + + fn delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), Error> { + match self.slots[slot.0 as usize].take() { + Some(_data) => Ok(()), + None => Err(km_err!(InvalidKeyBlob, "slot idx empty")), + } + } + + fn delete_all(&mut self) { + self.factory_secret = None; + for idx in 0..N { + self.slots[idx] = None; + } + } +} diff --git a/libs/rust/common/src/keyblob/tests.rs b/libs/rust/common/src/keyblob/tests.rs new file mode 100644 index 0000000..4c6b991 --- /dev/null +++ b/libs/rust/common/src/keyblob/tests.rs @@ -0,0 +1,90 @@ +// Copyright 2022, 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. + +use super::{ + sdd_mem::InMemorySlotManager, SecureDeletionSecretManager, SecureDeletionSlot, SlotHolder, + SlotPurpose, +}; + +#[derive(Default)] +struct FakeRng(u8); + +impl crate::crypto::Rng for FakeRng { + fn add_entropy(&mut self, _data: &[u8]) {} + fn fill_bytes(&mut self, dest: &mut [u8]) { + for b in dest { + *b = self.0; + self.0 += 1; + } + } +} + +#[test] +fn test_sdd_slot_holder() { + let mut sdd_mgr = InMemorySlotManager::<10>::default(); + let mut rng = FakeRng::default(); + let (slot_holder0, sdd0) = + SlotHolder::new(&mut sdd_mgr, &mut rng, SlotPurpose::KeyGeneration).unwrap(); + let slot0 = slot_holder0.consume(); + assert_eq!(slot0, SecureDeletionSlot(0)); + assert!(sdd_mgr.get_secret(slot0).unwrap() == sdd0); + + let (slot_holder1, sdd1) = + SlotHolder::new(&mut sdd_mgr, &mut rng, SlotPurpose::KeyGeneration).unwrap(); + let slot1 = slot_holder1.consume(); + assert_eq!(slot1, SecureDeletionSlot(1)); + assert!(sdd_mgr.get_secret(slot1).unwrap() == sdd1); + + assert!(sdd_mgr.get_secret(slot0).unwrap() == sdd0); + assert!(sdd_mgr.get_secret(slot1).unwrap() == sdd1); + assert!(sdd0 != sdd1); + + // If the slot holder is dropped rather than consumed, it should free the slot. + let (slot_holder2, _sdd2a) = + SlotHolder::new(&mut sdd_mgr, &mut rng, SlotPurpose::KeyGeneration).unwrap(); + drop(slot_holder2); + assert!(sdd_mgr.get_secret(SecureDeletionSlot(2)).is_err()); + + // Slot 2 is available again. + let (slot_holder2, sdd2b) = + SlotHolder::new(&mut sdd_mgr, &mut rng, SlotPurpose::KeyGeneration).unwrap(); + let slot2 = slot_holder2.consume(); + assert_eq!(slot2, SecureDeletionSlot(2)); + assert!(sdd_mgr.get_secret(slot2).unwrap() == sdd2b); +} + +#[test] +fn test_sdd_factory_secret() { + let mut sdd_mgr = InMemorySlotManager::<10>::default(); + let mut rng = FakeRng::default(); + assert!(sdd_mgr.get_factory_reset_secret().is_err()); + let secret1 = sdd_mgr.get_or_create_factory_reset_secret(&mut rng).unwrap(); + let secret2 = sdd_mgr.get_factory_reset_secret().unwrap(); + assert!(secret1 == secret2); + let secret3 = sdd_mgr.get_or_create_factory_reset_secret(&mut rng).unwrap(); + assert!(secret1 == secret3); +} + +#[test] +fn test_sdd_exhaustion() { + let mut sdd_mgr = InMemorySlotManager::<2>::default(); + let mut rng = FakeRng::default(); + let (_slot0, _sdd0) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + let (slot1a, sdd1a) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + assert!(sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).is_err()); + sdd_mgr.delete_secret(slot1a).unwrap(); + let (slot1b, sdd1b) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + assert_eq!(slot1a, slot1b); + assert!(sdd1a != sdd1b); +} diff --git a/libs/rust/common/src/lib.rs b/libs/rust/common/src/lib.rs new file mode 100644 index 0000000..cc62191 --- /dev/null +++ b/libs/rust/common/src/lib.rs @@ -0,0 +1,215 @@ +// Copyright 2022, 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. + +//! Functionality for KeyMint implementation that is common across HAL and TA. + +use core::convert::From; +use der::ErrorKind; +use kmr_wire::{cbor, keymint::ErrorCode, rpc, CborError}; + +pub use kmr_wire as wire; + +pub mod crypto; +pub mod keyblob; +pub mod tag; + +/// General error type. +#[derive(Debug)] +pub enum Error { + /// Error from CBOR conversion. + Cbor(CborError), + /// Error from ASN.1 DER conversion. + Der(ErrorKind), + /// Error as reported on the HAL interface. + /// + /// The `IKeyMintDevice`, `ISharedSecret` and `ISecureClock` HALs all share the same numbering + /// space for error codes, encoded here as [`kmr_wire::keymint::ErrorCode`]. + Hal(ErrorCode, String), + /// Error as reported on the `IRemotelyProvisionedComponent` HAL, which uses its own error + /// codes. + Rpc(rpc::ErrorCode, String), + /// Memory allocation error. + /// + /// This holds a string literal rather than an allocated `String` to avoid allocating in an + /// allocation error path. + Alloc(&'static str), +} + +// The following macros for error generation allow the message portion to be automatically +// compiled out in future, avoiding potential information leakage and allocation. + +/// Macro to build an [`Error::Hal`] instance for a specific [`ErrorCode`] value known at compile +/// time: `km_err!(InvalidTag, "some {} format", arg)`. +#[macro_export] +macro_rules! km_err { + { $error_code:ident, $($arg:tt)+ } => { + $crate::Error::Hal(kmr_wire::keymint::ErrorCode::$error_code, + format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+))) }; +} + +/// Macro to build an [`Error::Hal`] instance: +/// `km_verr!(rc, "some {} format", arg)`. +#[macro_export] +macro_rules! km_verr { + { $error_code:expr, $($arg:tt)+ } => { + $crate::Error::Hal($error_code, + format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+))) }; +} + +/// Macro to build an [`Error::Alloc`] instance. Note that this builds a `&'static str` at compile +/// time, so there is no allocation needed for the message (which would be failure-prone when +/// dealing with an allocation failure). +#[macro_export] +macro_rules! alloc_err { + { $len:expr } => { + $crate::Error::Alloc( + concat!(file!(), ":", line!(), ": failed allocation of size ", stringify!($len)) + ) + } +} + +/// Macro to build an [`Error::Der`] instance from a [`der::Error`]. +#[macro_export] +macro_rules! der_err { + { $err:expr, $($arg:tt)+ } => { + { + log::warn!("{}: {:?} at {:?}", format_args!($($arg)+), $err, $err.position()); + $crate::Error::Der($err.kind()) + } + } +} + +/// Macro to build an [`Error::Rpc`] instance for a specific [`rpc::ErrorCode`] value known at +/// compile time: `rpc_err!(Removed, "some {} format", arg)`. +#[macro_export] +macro_rules! rpc_err { + { $error_code:ident, $($arg:tt)+ } => { + $crate::Error::Rpc(kmr_wire::rpc::ErrorCode::$error_code, + std::format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+))) }; +} + +/// Macro to allocate a `Vec` with the given length reserved, detecting allocation failure. +#[macro_export] +macro_rules! vec_try_with_capacity { + { $len:expr} => { + { + let mut v = Vec::new(); + match v.try_reserve($len) { + Err(_e) => Err($crate::alloc_err!($len)), + Ok(_) => Ok(v), + } + } + } +} + +/// Macro that mimics `vec!` but which detects allocation failure. +#[macro_export] +macro_rules! vec_try { + { $elem:expr ; $len:expr } => { + kmr_wire::vec_try_fill_with_alloc_err($elem, $len, || $crate::alloc_err!($len)) + }; + { $x1:expr, $x2:expr, $x3:expr, $x4:expr $(,)? } => { + kmr_wire::vec_try4_with_alloc_err($x1, $x2, $x3, $x4, || $crate::alloc_err!(4)) + }; + { $x1:expr, $x2:expr, $x3:expr $(,)? } => { + kmr_wire::vec_try3_with_alloc_err($x1, $x2, $x3, || $crate::alloc_err!(3)) + }; + { $x1:expr, $x2:expr $(,)? } => { + kmr_wire::vec_try2_with_alloc_err($x1, $x2, || $crate::alloc_err!(2)) + }; + { $x1:expr $(,)? } => { + kmr_wire::vec_try1_with_alloc_err($x1, || $crate::alloc_err!(1)) + }; +} + +/// Function that mimics `slice.to_vec()` but which detects allocation failures. +#[inline] +pub fn try_to_vec(s: &[T]) -> Result, Error> { + let mut v = vec_try_with_capacity!(s.len())?; + v.extend_from_slice(s); + Ok(v) +} + +/// Extension trait to provide fallible-allocation variants of `Vec` methods. +pub trait FallibleAllocExt { + /// Try to add the `value` to the collection, failing on memory exhaustion. + fn try_push(&mut self, value: T) -> Result<(), std::collections::TryReserveError>; + /// Try to extend the collection with the contents of `other`, failing on memory exhaustion. + fn try_extend_from_slice( + &mut self, + other: &[T], + ) -> Result<(), std::collections::TryReserveError> + where + T: Clone; +} + +impl FallibleAllocExt for Vec { + fn try_push(&mut self, value: T) -> Result<(), std::collections::TryReserveError> { + self.try_reserve(1)?; + self.push(value); + Ok(()) + } + fn try_extend_from_slice( + &mut self, + other: &[T], + ) -> Result<(), std::collections::TryReserveError> + where + T: Clone, + { + self.try_reserve(other.len())?; + self.extend_from_slice(other); + Ok(()) + } +} + +impl From for Error { + fn from(_e: std::collections::TryReserveError) -> Self { + Error::Hal( + kmr_wire::keymint::ErrorCode::MemoryAllocationFailed, + "allocation of Vec failed".to_string(), + ) + } +} + +impl From for Error { + fn from(e: CborError) -> Self { + Error::Cbor(e) + } +} + +impl From for Error { + fn from(e: cbor::value::Error) -> Self { + Self::Cbor(e.into()) + } +} + +/// Check for an expected error. +#[macro_export] +macro_rules! expect_err { + ($result:expr, $err_msg:expr) => { + assert!( + $result.is_err(), + "Expected error containing '{}', got success {:?}", + $err_msg, + $result + ); + let err = $result.err(); + assert!( + format!("{:?}", err).contains($err_msg), + "Unexpected error {:?}, doesn't contain '{}'", + err, + $err_msg + ); + }; +} diff --git a/libs/rust/common/src/tag.rs b/libs/rust/common/src/tag.rs new file mode 100644 index 0000000..7b0e96e --- /dev/null +++ b/libs/rust/common/src/tag.rs @@ -0,0 +1,1429 @@ +// Copyright 2022, 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. + +//! Helper functionality for working with tags. + +use crate::{ + crypto, + crypto::{rsa::DecryptionMode, *}, + km_err, try_to_vec, vec_try_with_capacity, Error, FallibleAllocExt, +}; +use kmr_wire::{ + keymint::{ + Algorithm, BlockMode, Digest, EcCurve, ErrorCode, KeyCharacteristics, KeyFormat, KeyParam, + KeyPurpose, PaddingMode, SecurityLevel, Tag, DEFAULT_CERT_SERIAL, DEFAULT_CERT_SUBJECT, + }, + KeySizeInBits, +}; +use log::{info, warn}; + +mod info; +pub use info::*; +pub mod legacy; +#[cfg(test)] +mod tests; + +/// The set of tags that are directly copied from key generation/import parameters to +/// key characteristics without being checked. +pub const UNPOLICED_COPYABLE_TAGS: &[Tag] = &[ + Tag::RollbackResistance, + Tag::EarlyBootOnly, + Tag::MaxUsesPerBoot, + Tag::UserSecureId, // repeatable + Tag::NoAuthRequired, + Tag::UserAuthType, + Tag::AuthTimeout, + Tag::TrustedUserPresenceRequired, + Tag::TrustedConfirmationRequired, + Tag::UnlockedDeviceRequired, + Tag::StorageKey, +]; + +/// Indication of whether secure storage is available. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum SecureStorage { + /// Device has secure storage. + Available, + /// Device does not have secure storage. + Unavailable, +} + +/// Macro to retrieve a copy of the (single) value of a tag in a collection of `KeyParam`s. There +/// can be only one. Only works for variants whose data type implements `Copy`. +#[macro_export] +macro_rules! get_tag_value { + { $params:expr, $variant:ident, $err:expr } => { + { + let mut result = None; + let mut count = 0; + for param in $params { + if let kmr_wire::keymint::KeyParam::$variant(v) = param { + count += 1; + result = Some(*v); + } + } + match count { + 0 => Err($crate::km_verr!($err, "missing tag {}", stringify!($variant))), + 1 => Ok(result.unwrap()), /* safe: count=1 => exists */ + _ => Err($crate::km_verr!($err, "duplicate tag {}", stringify!($variant))), + } + } + } +} + +/// Macro to retrieve the value of an optional single-valued tag in a collection of `KeyParam`s. It +/// may or may not be present, but multiple instances of the tag are assumed to be invalid. +#[macro_export] +macro_rules! get_opt_tag_value { + { $params:expr, $variant:ident } => { + get_opt_tag_value!($params, $variant, InvalidTag) + }; + { $params:expr, $variant:ident, $dup_error:ident } => { + { + let mut result = None; + let mut count = 0; + for param in $params { + if let kmr_wire::keymint::KeyParam::$variant(v) = param { + count += 1; + result = Some(v); + } + } + match count { + 0 => Ok(None), + 1 => Ok(Some(result.unwrap())), /* safe: count=1 => exists */ + _ => Err($crate::km_err!($dup_error, "duplicate tag {}", stringify!($variant))), + } + } + } +} + +/// Macro to retrieve a `bool` tag value, returning `false` if the tag is absent +#[macro_export] +macro_rules! get_bool_tag_value { + { $params:expr, $variant:ident } => { + { + let mut count = 0; + for param in $params { + if let kmr_wire::keymint::KeyParam::$variant = param { + count += 1; + } + } + match count { + 0 => Ok(false), + 1 => Ok(true), + _ => Err($crate::km_err!(InvalidTag, "duplicate tag {}", stringify!($variant))), + } + } + } +} + +/// Macro to check a collection of `KeyParam`s holds a value matching the given value. +#[macro_export] +macro_rules! contains_tag_value { + { $params:expr, $variant:ident, $value:expr } => { + { + let mut found = false; + for param in $params { + if let kmr_wire::keymint::KeyParam::$variant(v) = param { + if *v == $value { + found = true; + } + } + } + found + } + } +} + +/// Check that a set of [`KeyParam`]s is valid when considered as key characteristics. +pub fn characteristics_valid(characteristics: &[KeyParam]) -> Result<(), Error> { + let mut dup_checker = DuplicateTagChecker::default(); + for param in characteristics { + let tag = param.tag(); + dup_checker.add(tag)?; + if info(tag)?.characteristic == Characteristic::NotKeyCharacteristic { + return Err( + km_err!(InvalidKeyBlob, "tag {:?} is not a valid key characteristic", tag,), + ); + } + } + Ok(()) +} + +/// Copy anything in `src` that matches `tags` into `dest`. Fails if any non-repeatable +/// tags occur more than once with a different value. +pub fn transcribe_tags( + dest: &mut Vec, + src: &[KeyParam], + tags: &[Tag], +) -> Result<(), Error> { + let mut dup_checker = DuplicateTagChecker::default(); + for param in src { + let tag = param.tag(); + dup_checker.add(tag)?; + if tags.iter().any(|t| *t == tag) { + dest.try_push(param.clone())?; + } + } + Ok(()) +} + +/// Get the configured algorithm from a set of parameters. +pub fn get_algorithm(params: &[KeyParam]) -> Result { + get_tag_value!(params, Algorithm, ErrorCode::UnsupportedAlgorithm) +} + +/// Get the configured block mode from a set of parameters. +pub fn get_block_mode(params: &[KeyParam]) -> Result { + get_tag_value!(params, BlockMode, ErrorCode::UnsupportedBlockMode) +} + +/// Get the configured padding mode from a set of parameters. +pub fn get_padding_mode(params: &[KeyParam]) -> Result { + get_tag_value!(params, Padding, ErrorCode::UnsupportedPaddingMode) +} + +/// Get the configured digest from a set of parameters. +pub fn get_digest(params: &[KeyParam]) -> Result { + get_tag_value!(params, Digest, ErrorCode::UnsupportedDigest) +} + +/// Get the configured elliptic curve from a set of parameters. +pub fn get_ec_curve(params: &[KeyParam]) -> Result { + get_tag_value!(params, EcCurve, ErrorCode::UnsupportedKeySize) +} + +/// Get the configured MGF digest from a set of parameters. If no MGF digest is specified, +/// a default value of SHA1 is returned. +pub fn get_mgf_digest(params: &[KeyParam]) -> Result { + Ok(*get_opt_tag_value!(params, RsaOaepMgfDigest)?.unwrap_or(&Digest::Sha1)) +} + +/// Get the certificate serial number from a set of parameters, falling back to default value of 1 +/// if not specified +pub fn get_cert_serial(params: &[KeyParam]) -> Result<&[u8], Error> { + Ok(get_opt_tag_value!(params, CertificateSerial)? + .map(Vec::as_ref) + .unwrap_or(DEFAULT_CERT_SERIAL)) +} + +/// Return the set of key parameters at the provided security level. +pub fn characteristics_at( + chars: &[KeyCharacteristics], + sec_level: SecurityLevel, +) -> Result<&[KeyParam], Error> { + let mut result: Option<&[KeyParam]> = None; + for chars in chars { + if chars.security_level != sec_level { + continue; + } + if result.is_none() { + result = Some(&chars.authorizations); + } else { + return Err(km_err!(InvalidKeyBlob, "multiple key characteristics at {:?}", sec_level)); + } + } + result.ok_or_else(|| { + km_err!(InvalidKeyBlob, "no parameters at security level {:?} found", sec_level) + }) +} + +/// Get the certificate subject from a set of parameters, falling back to a default if not +/// specified. +pub fn get_cert_subject(params: &[KeyParam]) -> Result<&[u8], Error> { + Ok(get_opt_tag_value!(params, CertificateSubject)? + .map(Vec::as_ref) + .unwrap_or(DEFAULT_CERT_SUBJECT)) +} + +/// Build the parameters that are used as the hidden input to KEK derivation calculations: +/// - `ApplicationId(data)` if present +/// - `ApplicationData(data)` if present +/// - `RootOfTrust(rot)` where `rot` is a hardcoded root of trust +pub fn hidden(params: &[KeyParam], rot: &[u8]) -> Result, Error> { + let mut results = vec_try_with_capacity!(3)?; + if let Ok(Some(app_id)) = get_opt_tag_value!(params, ApplicationId) { + results.push(KeyParam::ApplicationId(try_to_vec(app_id)?)); + } + if let Ok(Some(app_data)) = get_opt_tag_value!(params, ApplicationData) { + results.push(KeyParam::ApplicationData(try_to_vec(app_data)?)); + } + results.push(KeyParam::RootOfTrust(try_to_vec(rot)?)); + Ok(results) +} + +/// Build the set of key characteristics for a key that is about to be generated, +/// checking parameter validity along the way. Also return the information needed for key +/// generation. +pub fn extract_key_gen_characteristics( + secure_storage: SecureStorage, + params: &[KeyParam], + sec_level: SecurityLevel, +) -> Result<(Vec, KeyGenInfo), Error> { + let keygen_info = match get_algorithm(params)? { + Algorithm::Rsa => check_rsa_gen_params(params, sec_level), + Algorithm::Ec => check_ec_gen_params(params, sec_level), + Algorithm::Aes => check_aes_gen_params(params, sec_level), + Algorithm::TripleDes => check_3des_gen_params(params), + Algorithm::Hmac => check_hmac_gen_params(params, sec_level), + }?; + Ok((extract_key_characteristics(secure_storage, params, &[], sec_level)?, keygen_info)) +} + +/// Build the set of key characteristics for a key that is about to be imported, +/// checking parameter validity along the way. +pub fn extract_key_import_characteristics( + imp: &crypto::Implementation, + secure_storage: SecureStorage, + params: &[KeyParam], + sec_level: SecurityLevel, + key_format: KeyFormat, + key_data: &[u8], +) -> Result<(Vec, KeyMaterial), Error> { + let (deduced_params, key_material) = match get_algorithm(params)? { + Algorithm::Rsa => { + check_rsa_import_params(&*imp.rsa, params, sec_level, key_format, key_data) + } + Algorithm::Ec => check_ec_import_params(&*imp.ec, params, sec_level, key_format, key_data), + Algorithm::Aes => { + check_aes_import_params(&*imp.aes, params, sec_level, key_format, key_data) + } + Algorithm::TripleDes => check_3des_import_params(&*imp.des, params, key_format, key_data), + Algorithm::Hmac => { + check_hmac_import_params(&*imp.hmac, params, sec_level, key_format, key_data) + } + }?; + Ok(( + extract_key_characteristics(secure_storage, params, &deduced_params, sec_level)?, + key_material, + )) +} + +/// Build the set of key characteristics for a key that is about to be generated or imported, +/// checking parameter validity along the way. The `extra_params` argument provides additional +/// parameters on top of `params`, such as those deduced from imported key material. +fn extract_key_characteristics( + secure_storage: SecureStorage, + params: &[KeyParam], + extra_params: &[KeyParam], + sec_level: SecurityLevel, +) -> Result, Error> { + // Separately accumulate any characteristics that are policed by Keystore. + let mut chars = Vec::new(); + let mut keystore_chars = Vec::new(); + for param in params.iter().chain(extra_params) { + let tag = param.tag(); + + // Input params should not contain anything that KeyMint adds itself. + if AUTO_ADDED_CHARACTERISTICS.contains(&tag) { + return Err(km_err!(InvalidTag, "KeyMint-added tag included on key generation/import")); + } + + if sec_level == SecurityLevel::Strongbox + && [Tag::MaxUsesPerBoot, Tag::RollbackResistance].contains(&tag) + { + // StrongBox does not support tags that require per-key storage. + return Err(km_err!(InvalidTag, "tag {:?} not allowed in StrongBox", param.tag())); + } + + // UsageCountLimit is peculiar. If its value is > 1, it should be Keystore-enforced. + // If its value is = 1, then it is KeyMint-enforced if secure storage is available, + // and Keystore-enforced otherwise. + if let KeyParam::UsageCountLimit(use_limit) = param { + match (use_limit, secure_storage) { + (1, SecureStorage::Available) => { + chars.try_push(KeyParam::UsageCountLimit(*use_limit))? + } + (1, SecureStorage::Unavailable) | (_, _) => { + keystore_chars.try_push(KeyParam::UsageCountLimit(*use_limit))? + } + } + } + + if KEYMINT_ENFORCED_CHARACTERISTICS.contains(&tag) { + chars.try_push(param.clone())?; + } else if KEYSTORE_ENFORCED_CHARACTERISTICS.contains(&tag) { + keystore_chars.try_push(param.clone())?; + } + } + + reject_incompatible_auth(&chars)?; + + // Use the same sort order for tags as was previously used. + chars.sort_by(legacy::param_compare); + keystore_chars.sort_by(legacy::param_compare); + + let mut result = Vec::new(); + result.try_push(KeyCharacteristics { security_level: sec_level, authorizations: chars })?; + if !keystore_chars.is_empty() { + result.try_push(KeyCharacteristics { + security_level: SecurityLevel::Keystore, + authorizations: keystore_chars, + })?; + } + Ok(result) +} + +/// Check that an RSA key size is valid. +fn check_rsa_key_size(key_size: KeySizeInBits, sec_level: SecurityLevel) -> Result<(), Error> { + // StrongBox only supports 2048-bit keys. + match key_size { + KeySizeInBits(512) if sec_level != SecurityLevel::Strongbox => Ok(()), + KeySizeInBits(768) if sec_level != SecurityLevel::Strongbox => Ok(()), + KeySizeInBits(1024) if sec_level != SecurityLevel::Strongbox => Ok(()), + KeySizeInBits(2048) => Ok(()), + KeySizeInBits(3072) if sec_level != SecurityLevel::Strongbox => Ok(()), + KeySizeInBits(4096) if sec_level != SecurityLevel::Strongbox => Ok(()), + _ => Err(km_err!(UnsupportedKeySize, "unsupported KEY_SIZE {:?} bits for RSA", key_size)), + } +} + +/// Check RSA key generation parameter validity. +fn check_rsa_gen_params( + params: &[KeyParam], + sec_level: SecurityLevel, +) -> Result { + // For key generation, size and public exponent must be explicitly specified. + let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?; + check_rsa_key_size(key_size, sec_level)?; + let public_exponent = get_tag_value!(params, RsaPublicExponent, ErrorCode::InvalidArgument)?; + + check_rsa_params(params)?; + Ok(KeyGenInfo::Rsa(key_size, public_exponent)) +} + +/// Check RSA key import parameter validity. Return the key material along with any key generation +/// parameters that have been deduced from the key material (but which are not present in the input +/// key parameters). +fn check_rsa_import_params( + rsa: &dyn Rsa, + params: &[KeyParam], + sec_level: SecurityLevel, + key_format: KeyFormat, + key_data: &[u8], +) -> Result<(Vec, KeyMaterial), Error> { + // Deduce key size and exponent from import data. + if key_format != KeyFormat::Pkcs8 { + return Err(km_err!( + UnsupportedKeyFormat, + "unsupported import format {:?}, expect PKCS8", + key_format + )); + } + let (key, key_size, public_exponent) = rsa.import_pkcs8_key(key_data, params)?; + + // If key size or exponent are explicitly specified, they must match. If they were not + // specified, we emit them. + let mut deduced_chars = Vec::new(); + match get_opt_tag_value!(params, KeySize)? { + Some(param_key_size) => { + if *param_key_size != key_size { + return Err(km_err!( + ImportParameterMismatch, + "specified KEY_SIZE {:?} bits != actual key size {:?} for PKCS8 import", + param_key_size, + key_size + )); + } + } + None => deduced_chars.try_push(KeyParam::KeySize(key_size))?, + } + match get_opt_tag_value!(params, RsaPublicExponent)? { + Some(param_public_exponent) => { + if *param_public_exponent != public_exponent { + return Err(km_err!( + ImportParameterMismatch, + "specified RSA_PUBLIC_EXPONENT {:?} != actual exponent {:?} for PKCS8 import", + param_public_exponent, + public_exponent, + )); + } + } + None => deduced_chars.try_push(KeyParam::RsaPublicExponent(public_exponent))?, + } + check_rsa_key_size(key_size, sec_level)?; + + check_rsa_params(params)?; + Ok((deduced_chars, key)) +} + +/// Check the parameter validity for an RSA key that is about to be generated or imported. +fn check_rsa_params(params: &[KeyParam]) -> Result<(), Error> { + let mut seen_attest = false; + let mut seen_non_attest = false; + for param in params { + if let KeyParam::Purpose(purpose) = param { + match purpose { + KeyPurpose::Sign | KeyPurpose::Decrypt | KeyPurpose::WrapKey => { + seen_non_attest = true + } + KeyPurpose::AttestKey => seen_attest = true, + KeyPurpose::Verify | KeyPurpose::Encrypt => {} // public key operations + KeyPurpose::AgreeKey => { + warn!("Generating RSA key with invalid purpose {:?}", purpose) + } + } + } + } + if seen_attest && seen_non_attest { + return Err(km_err!( + IncompatiblePurpose, + "keys with ATTEST_KEY must have no other purpose" + )); + } + Ok(()) +} + +/// Check EC key generation parameter validity. +fn check_ec_gen_params(params: &[KeyParam], sec_level: SecurityLevel) -> Result { + // For key generation, the curve must be explicitly specified. + let ec_curve = get_ec_curve(params)?; + + let purpose = check_ec_params(ec_curve, params, sec_level)?; + let keygen_info = match (ec_curve, purpose) { + (EcCurve::Curve25519, Some(KeyPurpose::Sign)) => KeyGenInfo::Ed25519, + (EcCurve::Curve25519, Some(KeyPurpose::AttestKey)) => KeyGenInfo::Ed25519, + (EcCurve::Curve25519, Some(KeyPurpose::AgreeKey)) => KeyGenInfo::X25519, + (EcCurve::Curve25519, _) => { + return Err(km_err!( + IncompatiblePurpose, + "curve25519 keys with invalid purpose {:?}", + purpose + )) + } + (EcCurve::P224, _) => KeyGenInfo::NistEc(ec::NistCurve::P224), + (EcCurve::P256, _) => KeyGenInfo::NistEc(ec::NistCurve::P256), + (EcCurve::P384, _) => KeyGenInfo::NistEc(ec::NistCurve::P384), + (EcCurve::P521, _) => KeyGenInfo::NistEc(ec::NistCurve::P521), + }; + Ok(keygen_info) +} + +/// Find the first purpose value in the parameters. +pub fn primary_purpose(params: &[KeyParam]) -> Result { + params + .iter() + .find_map( + |param| if let KeyParam::Purpose(purpose) = param { Some(*purpose) } else { None }, + ) + .ok_or_else(|| km_err!(IncompatiblePurpose, "no purpose found for key!")) +} + +/// Check EC key import parameter validity. Return the key material along with any key generation +/// parameters that have been deduced from the key material (but which are not present in the input +/// key parameters). +fn check_ec_import_params( + ec: &dyn Ec, + params: &[KeyParam], + sec_level: SecurityLevel, + key_format: KeyFormat, + key_data: &[u8], +) -> Result<(Vec, KeyMaterial), Error> { + // Curve25519 can be imported as PKCS8 or raw; all other curves must be PKCS8. + // If we need to disinguish between Ed25519 and X25519, we need to examine the purpose for the + // key -- look for `AgreeKey` as it cannot be combined with other purposes. + let (key, curve) = match key_format { + KeyFormat::Raw if get_ec_curve(params)? == EcCurve::Curve25519 => { + // Raw key import must specify the curve (and the only valid option is Curve25519 + // currently). + if primary_purpose(params)? == KeyPurpose::AgreeKey { + (ec.import_raw_x25519_key(key_data, params)?, EcCurve::Curve25519) + } else { + (ec.import_raw_ed25519_key(key_data, params)?, EcCurve::Curve25519) + } + } + KeyFormat::Pkcs8 => { + let key = ec.import_pkcs8_key(key_data, params)?; + let curve = match &key { + KeyMaterial::Ec(curve, CurveType::Nist, _) => *curve, + KeyMaterial::Ec(EcCurve::Curve25519, CurveType::EdDsa, _) => { + if primary_purpose(params)? == KeyPurpose::AgreeKey { + return Err(km_err!( + IncompatiblePurpose, + "can't use EdDSA key for key agreement" + )); + } + EcCurve::Curve25519 + } + KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, _) => { + if primary_purpose(params)? != KeyPurpose::AgreeKey { + return Err(km_err!(IncompatiblePurpose, "can't use XDH key for signing")); + } + EcCurve::Curve25519 + } + _ => { + return Err(km_err!( + ImportParameterMismatch, + "unexpected key type from EC import" + )) + } + }; + (key, curve) + } + _ => { + return Err(km_err!( + UnsupportedKeyFormat, + "invalid import format ({:?}) for EC key", + key_format, + )); + } + }; + + // If curve was explicitly specified, it must match. If not specified, populate it in the + // deduced characteristics. + let mut deduced_chars = Vec::new(); + match get_opt_tag_value!(params, EcCurve)? { + Some(specified_curve) => { + if *specified_curve != curve { + return Err(km_err!( + ImportParameterMismatch, + "imported EC key claimed curve {:?} but is {:?}", + specified_curve, + curve + )); + } + } + None => deduced_chars.try_push(KeyParam::EcCurve(curve))?, + } + + // If key size was explicitly specified, it must match. If not specified, populate it in the + // deduced characteristics. + let key_size = ec::curve_to_key_size(curve); + match get_opt_tag_value!(params, KeySize)? { + Some(param_key_size) => { + if *param_key_size != key_size { + return Err(km_err!( + ImportParameterMismatch, + "specified KEY_SIZE {:?} bits != actual key size {:?} for PKCS8 import", + param_key_size, + key_size + )); + } + } + None => deduced_chars.try_push(KeyParam::KeySize(key_size))?, + } + + check_ec_params(curve, params, sec_level)?; + Ok((deduced_chars, key)) +} + +/// Check the parameter validity for an EC key that is about to be generated or imported. +fn check_ec_params( + curve: EcCurve, + params: &[KeyParam], + sec_level: SecurityLevel, +) -> Result, Error> { + if sec_level == SecurityLevel::Strongbox && curve != EcCurve::P256 { + return Err(km_err!(UnsupportedEcCurve, "invalid curve ({:?}) for StrongBox", curve)); + } + + // Key size is not needed, but if present should match the curve. + if let Some(key_size) = get_opt_tag_value!(params, KeySize)? { + match curve { + EcCurve::P224 if *key_size == KeySizeInBits(224) => {} + EcCurve::P256 if *key_size == KeySizeInBits(256) => {} + EcCurve::P384 if *key_size == KeySizeInBits(384) => {} + EcCurve::P521 if *key_size == KeySizeInBits(521) => {} + EcCurve::Curve25519 if *key_size == KeySizeInBits(256) => {} + _ => { + return Err(km_err!( + InvalidArgument, + "invalid curve ({:?}) / key size ({:?}) combination", + curve, + key_size + )) + } + } + } + + let mut seen_attest = false; + let mut seen_sign = false; + let mut seen_agree = false; + let mut primary_purpose = None; + for param in params { + if let KeyParam::Purpose(purpose) = param { + match purpose { + KeyPurpose::Sign => seen_sign = true, + KeyPurpose::AgreeKey => seen_agree = true, + KeyPurpose::AttestKey => seen_attest = true, + KeyPurpose::Verify => {} + _ => warn!("Generating EC key with invalid purpose {:?}", purpose), + } + if primary_purpose.is_none() { + primary_purpose = Some(*purpose); + } + } + } + // Keys with Purpose::ATTEST_KEY must have no other purpose. + if seen_attest && (seen_sign || seen_agree) { + return Err(km_err!( + IncompatiblePurpose, + "keys with ATTEST_KEY must have no other purpose" + )); + } + // Curve25519 keys must be either signing/attesting keys (Ed25519), or key agreement + // keys (X25519), not both. + if curve == EcCurve::Curve25519 && seen_agree && (seen_sign || seen_attest) { + return Err(km_err!( + IncompatiblePurpose, + "curve25519 keys must be either SIGN/ATTEST_KEY or AGREE_KEY, not both" + )); + } + + Ok(primary_purpose) +} + +/// Check AES key generation parameter validity. +fn check_aes_gen_params( + params: &[KeyParam], + sec_level: SecurityLevel, +) -> Result { + // For key generation, the size must be explicitly specified. + let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?; + + let keygen_info = match key_size { + KeySizeInBits(128) => KeyGenInfo::Aes(aes::Variant::Aes128), + KeySizeInBits(256) => KeyGenInfo::Aes(aes::Variant::Aes256), + KeySizeInBits(192) if sec_level != SecurityLevel::Strongbox => { + KeyGenInfo::Aes(aes::Variant::Aes192) + } + _ => { + return Err(km_err!( + UnsupportedKeySize, + "unsupported KEY_SIZE {:?} bits for AES", + key_size + )) + } + }; + + check_aes_params(params)?; + Ok(keygen_info) +} + +/// Check AES key import parameter validity. Return the key material along with any key generation +/// parameters that have been deduced from the key material (but which are not present in the input +/// key parameters). +fn check_aes_import_params( + aes: &dyn Aes, + params: &[KeyParam], + sec_level: SecurityLevel, + key_format: KeyFormat, + key_data: &[u8], +) -> Result<(Vec, KeyMaterial), Error> { + require_raw(key_format)?; + let (key, key_size) = aes.import_key(key_data, params)?; + if key_size == KeySizeInBits(192) && sec_level == SecurityLevel::Strongbox { + return Err(km_err!( + UnsupportedKeySize, + "unsupported KEY_SIZE=192 bits for AES on StrongBox", + )); + } + let deduced_chars = require_matching_key_size(params, key_size)?; + + check_aes_params(params)?; + Ok((deduced_chars, key)) +} + +/// Check the parameter validity for an AES key that is about to be generated or imported. +fn check_aes_params(params: &[KeyParam]) -> Result<(), Error> { + let gcm_support = params.iter().any(|p| *p == KeyParam::BlockMode(BlockMode::Gcm)); + if gcm_support { + let min_mac_len = get_tag_value!(params, MinMacLength, ErrorCode::MissingMinMacLength)?; + if (min_mac_len % 8 != 0) || !(96..=128).contains(&min_mac_len) { + return Err(km_err!( + UnsupportedMinMacLength, + "unsupported MIN_MAC_LENGTH {} bits", + min_mac_len + )); + } + } + Ok(()) +} + +/// Check triple DES key generation parameter validity. +fn check_3des_gen_params(params: &[KeyParam]) -> Result { + // For key generation, the size (168) must be explicitly specified. + let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?; + if key_size != KeySizeInBits(168) { + return Err(km_err!( + UnsupportedKeySize, + "unsupported KEY_SIZE {:?} bits for TRIPLE_DES", + key_size + )); + } + Ok(KeyGenInfo::TripleDes) +} + +/// Check triple DES key import parameter validity. Return the key material along with any key +/// generation parameters that have been deduced from the key material (but which are not present in +/// the input key parameters). +fn check_3des_import_params( + des: &dyn Des, + params: &[KeyParam], + key_format: KeyFormat, + key_data: &[u8], +) -> Result<(Vec, KeyMaterial), Error> { + require_raw(key_format)?; + let key = des.import_key(key_data, params)?; + // If the key size is specified as a parameter, it must be 168. Note that this + // is not equal to 8 x 24 (the data size). + let deduced_chars = require_matching_key_size(params, des::KEY_SIZE_BITS)?; + + Ok((deduced_chars, key)) +} + +/// Check HMAC key generation parameter validity. +fn check_hmac_gen_params( + params: &[KeyParam], + sec_level: SecurityLevel, +) -> Result { + // For key generation the size must be explicitly specified. + let key_size = get_tag_value!(params, KeySize, ErrorCode::UnsupportedKeySize)?; + check_hmac_params(params, sec_level, key_size)?; + Ok(KeyGenInfo::Hmac(key_size)) +} + +/// Build the set of key characteristics for an HMAC key that is about to be imported, +/// checking parameter validity along the way. +fn check_hmac_import_params( + hmac: &dyn Hmac, + params: &[KeyParam], + sec_level: SecurityLevel, + key_format: KeyFormat, + key_data: &[u8], +) -> Result<(Vec, KeyMaterial), Error> { + require_raw(key_format)?; + let (key, key_size) = hmac.import_key(key_data, params)?; + let deduced_chars = require_matching_key_size(params, key_size)?; + + check_hmac_params(params, sec_level, key_size)?; + Ok((deduced_chars, key)) +} + +/// Check the parameter validity for an HMAC key that is about to be generated or imported. +fn check_hmac_params( + params: &[KeyParam], + sec_level: SecurityLevel, + key_size: KeySizeInBits, +) -> Result<(), Error> { + if sec_level == SecurityLevel::Strongbox { + hmac::valid_strongbox_hal_size(key_size)?; + } else { + hmac::valid_hal_size(key_size)?; + } + let digest = get_tag_value!(params, Digest, ErrorCode::UnsupportedDigest)?; + if digest == Digest::None { + return Err(km_err!(UnsupportedDigest, "unsupported digest {:?}", digest)); + } + + let min_mac_len = get_tag_value!(params, MinMacLength, ErrorCode::MissingMinMacLength)?; + if (min_mac_len % 8 != 0) || !(64..=512).contains(&min_mac_len) { + return Err(km_err!( + UnsupportedMinMacLength, + "unsupported MIN_MAC_LENGTH {:?} bits", + min_mac_len + )); + } + Ok(()) +} + +/// Check for `KeyFormat::RAW`. +fn require_raw(key_format: KeyFormat) -> Result<(), Error> { + if key_format != KeyFormat::Raw { + return Err(km_err!( + UnsupportedKeyFormat, + "unsupported import format {:?}, expect RAW", + key_format + )); + } + Ok(()) +} + +/// Check or populate a `Tag::KEY_SIZE` value. +fn require_matching_key_size( + params: &[KeyParam], + key_size: KeySizeInBits, +) -> Result, Error> { + let mut deduced_chars = Vec::new(); + match get_opt_tag_value!(params, KeySize)? { + Some(param_key_size) => { + if *param_key_size != key_size { + return Err(km_err!( + ImportParameterMismatch, + "specified KEY_SIZE {:?} bits != actual key size {:?}", + param_key_size, + key_size + )); + } + } + None => deduced_chars.try_push(KeyParam::KeySize(key_size))?, + } + Ok(deduced_chars) +} + +/// Return an error if any of the `exclude` tags are found in `params`. +fn reject_tags(params: &[KeyParam], exclude: &[Tag]) -> Result<(), Error> { + for param in params { + if exclude.contains(¶m.tag()) { + return Err(km_err!(InvalidTag, "tag {:?} not allowed", param.tag())); + } + } + Ok(()) +} + +/// Return an error if non-None padding found. +fn reject_some_padding(params: &[KeyParam]) -> Result<(), Error> { + if let Some(padding) = get_opt_tag_value!(params, Padding)? { + if *padding != PaddingMode::None { + return Err(km_err!(InvalidTag, "padding {:?} not allowed", padding)); + } + } + Ok(()) +} + +/// Return an error if non-None digest found. +fn reject_some_digest(params: &[KeyParam]) -> Result<(), Error> { + if let Some(digest) = get_opt_tag_value!(params, Digest)? { + if *digest != Digest::None { + return Err(km_err!(InvalidTag, "digest {:?} not allowed", digest)); + } + } + Ok(()) +} + +/// Reject incompatible combinations of authentication tags. +fn reject_incompatible_auth(params: &[KeyParam]) -> Result<(), Error> { + let mut seen_user_secure_id = false; + let mut seen_auth_type = false; + let mut seen_no_auth = false; + + for param in params { + match param { + KeyParam::UserSecureId(_sid) => seen_user_secure_id = true, + KeyParam::UserAuthType(_atype) => seen_auth_type = true, + KeyParam::NoAuthRequired => seen_no_auth = true, + _ => {} + } + } + + if seen_no_auth { + if seen_user_secure_id { + return Err(km_err!(InvalidTag, "found both NO_AUTH_REQUIRED and USER_SECURE_ID")); + } + if seen_auth_type { + return Err(km_err!(InvalidTag, "found both NO_AUTH_REQUIRED and USER_AUTH_TYPE")); + } + } + if seen_user_secure_id && !seen_auth_type { + return Err(km_err!(InvalidTag, "found USER_SECURE_ID but no USER_AUTH_TYPE")); + } + Ok(()) +} + +/// Indication of which parameters on a `begin` need to be checked against key authorizations. +struct BeginParamsToCheck { + block_mode: bool, + padding: bool, + digest: bool, + mgf_digest: bool, +} + +/// Check that an operation with the given `purpose` and `params` can validly be started +/// using a key with characteristics `chars`. +pub fn check_begin_params( + chars: &[KeyParam], + purpose: KeyPurpose, + params: &[KeyParam], +) -> Result<(), Error> { + // General checks for all algorithms. + let algo = get_algorithm(chars)?; + let valid_purpose = matches!( + (algo, purpose), + (Algorithm::Aes, KeyPurpose::Encrypt) + | (Algorithm::Aes, KeyPurpose::Decrypt) + | (Algorithm::TripleDes, KeyPurpose::Encrypt) + | (Algorithm::TripleDes, KeyPurpose::Decrypt) + | (Algorithm::Hmac, KeyPurpose::Sign) + | (Algorithm::Hmac, KeyPurpose::Verify) + | (Algorithm::Ec, KeyPurpose::Sign) + | (Algorithm::Ec, KeyPurpose::AttestKey) + | (Algorithm::Ec, KeyPurpose::AgreeKey) + | (Algorithm::Rsa, KeyPurpose::Sign) + | (Algorithm::Rsa, KeyPurpose::Decrypt) + | (Algorithm::Rsa, KeyPurpose::AttestKey) + ); + if !valid_purpose { + return Err(km_err!( + UnsupportedPurpose, + "invalid purpose {:?} for {:?} key", + purpose, + algo + )); + } + if !contains_tag_value!(chars, Purpose, purpose) { + return Err(km_err!( + IncompatiblePurpose, + "purpose {:?} not in key characteristics", + purpose + )); + } + if get_bool_tag_value!(chars, StorageKey)? { + return Err(km_err!(StorageKeyUnsupported, "attempt to use storage key",)); + } + let nonce = get_opt_tag_value!(params, Nonce)?; + if get_bool_tag_value!(chars, CallerNonce)? { + // Caller-provided nonces are allowed. + } else if nonce.is_some() && purpose == KeyPurpose::Encrypt { + return Err(km_err!(CallerNonceProhibited, "caller nonce not allowed for encryption")); + } + + // Further algorithm-specific checks. + let check = match algo { + Algorithm::Rsa => check_begin_rsa_params(chars, purpose, params), + Algorithm::Ec => check_begin_ec_params(chars, purpose, params), + Algorithm::Aes => check_begin_aes_params(chars, params, nonce.map(|v| v.as_ref())), + Algorithm::TripleDes => check_begin_3des_params(params, nonce.map(|v| v.as_ref())), + Algorithm::Hmac => check_begin_hmac_params(chars, purpose, params), + }?; + + // For various parameters, if they are specified in the begin parameters and they + // are relevant for the algorithm, then the same value must also exist in the key + // characteristics. Also, there can be only one distinct value in the parameters. + if check.block_mode { + if let Some(bmode) = get_opt_tag_value!(params, BlockMode, UnsupportedBlockMode)? { + if !contains_tag_value!(chars, BlockMode, *bmode) { + return Err(km_err!( + IncompatibleBlockMode, + "block mode {:?} not in key characteristics {:?}", + bmode, + chars, + )); + } + } + } + if check.padding { + if let Some(pmode) = get_opt_tag_value!(params, Padding, UnsupportedPaddingMode)? { + if !contains_tag_value!(chars, Padding, *pmode) { + return Err(km_err!( + IncompatiblePaddingMode, + "padding mode {:?} not in key characteristics {:?}", + pmode, + chars, + )); + } + } + } + if check.digest { + if let Some(digest) = get_opt_tag_value!(params, Digest, UnsupportedDigest)? { + if !contains_tag_value!(chars, Digest, *digest) { + return Err(km_err!( + IncompatibleDigest, + "digest {:?} not in key characteristics", + digest, + )); + } + } + } + if check.mgf_digest { + let mut mgf_digest_to_find = + get_opt_tag_value!(params, RsaOaepMgfDigest, UnsupportedMgfDigest)?; + + let chars_have_mgf_digest = + chars.iter().any(|param| matches!(param, KeyParam::RsaOaepMgfDigest(_))); + if chars_have_mgf_digest && mgf_digest_to_find.is_none() { + // The key characteristics include an explicit set of MGF digests, but the begin() + // operation is using the default SHA1. Check that this default is in the + // characteristics. + mgf_digest_to_find = Some(&Digest::Sha1); + } + + if let Some(mgf_digest) = mgf_digest_to_find { + if !contains_tag_value!(chars, RsaOaepMgfDigest, *mgf_digest) { + return Err(km_err!( + IncompatibleMgfDigest, + "MGF digest {:?} not in key characteristics", + mgf_digest, + )); + } + } + } + Ok(()) +} + +/// Indicate whether a [`KeyPurpose`] is for encryption/decryption. +fn for_encryption(purpose: KeyPurpose) -> bool { + purpose == KeyPurpose::Encrypt + || purpose == KeyPurpose::Decrypt + || purpose == KeyPurpose::WrapKey +} + +/// Indicate whether a [`KeyPurpose`] is for signing. +fn for_signing(purpose: KeyPurpose) -> bool { + purpose == KeyPurpose::Sign +} + +/// Check that an RSA operation with the given `purpose` and `params` can validly be started +/// using a key with characteristics `chars`. +fn check_begin_rsa_params( + chars: &[KeyParam], + purpose: KeyPurpose, + params: &[KeyParam], +) -> Result { + let padding = get_padding_mode(params)?; + let mut digest = None; + if for_signing(purpose) || (for_encryption(purpose) && padding == PaddingMode::RsaOaep) { + digest = Some(get_digest(params)?); + } + if for_signing(purpose) && padding == PaddingMode::None && digest != Some(Digest::None) { + return Err(km_err!( + IncompatibleDigest, + "unpadded RSA sign requires Digest::None not {:?}", + digest + )); + } + match padding { + PaddingMode::None => {} + PaddingMode::RsaOaep if for_encryption(purpose) => { + if digest.is_none() || digest == Some(Digest::None) { + return Err(km_err!(IncompatibleDigest, "digest required for RSA-OAEP")); + } + let mgf_digest = get_mgf_digest(params)?; + if mgf_digest == Digest::None { + return Err(km_err!( + UnsupportedMgfDigest, + "MGF digest cannot be NONE for RSA-OAEP" + )); + } + } + PaddingMode::RsaPss if for_signing(purpose) => { + if let Some(digest) = digest { + let key_size_bits = get_tag_value!(chars, KeySize, ErrorCode::InvalidArgument)?; + let d = digest_len(digest)?; + if key_size_bits < KeySizeInBits(2 * d + 9) { + return Err(km_err!( + IncompatibleDigest, + "key size {:?} < 2*8*D={} + 9", + key_size_bits, + d + )); + } + } else { + return Err(km_err!(IncompatibleDigest, "digest required for RSA-PSS")); + } + } + PaddingMode::RsaPkcs115Encrypt if for_encryption(purpose) => { + if digest.is_some() && digest != Some(Digest::None) { + warn!( + "ignoring digest {:?} provided for PKCS#1 v1.5 encryption/decryption", + digest + ); + } + } + PaddingMode::RsaPkcs115Sign if for_signing(purpose) => { + if digest.is_none() { + return Err(km_err!(IncompatibleDigest, "digest required for RSA-PKCS_1_5_SIGN")); + } + } + _ => { + return Err(km_err!( + UnsupportedPaddingMode, + "purpose {:?} incompatible with padding {:?}", + purpose, + padding + )) + } + } + + Ok(BeginParamsToCheck { block_mode: false, padding: true, digest: true, mgf_digest: true }) +} + +/// Check that an EC operation with the given `purpose` and `params` can validly be started +/// using a key with characteristics `chars`. +fn check_begin_ec_params( + chars: &[KeyParam], + purpose: KeyPurpose, + params: &[KeyParam], +) -> Result { + let curve = get_ec_curve(chars)?; + if purpose == KeyPurpose::Sign { + let digest = get_digest(params)?; + if digest == Digest::Md5 { + return Err(km_err!(UnsupportedDigest, "Digest::MD5 unsupported for EC signing")); + } + if curve == EcCurve::Curve25519 && digest != Digest::None { + return Err(km_err!( + UnsupportedDigest, + "Ed25519 only supports Digest::None not {:?}", + digest + )); + } + } + Ok(BeginParamsToCheck { block_mode: false, padding: false, digest: true, mgf_digest: false }) +} + +/// Check that an AES operation with the given `purpose` and `params` can validly be started +/// using a key with characteristics `chars`. +fn check_begin_aes_params( + chars: &[KeyParam], + params: &[KeyParam], + caller_nonce: Option<&[u8]>, +) -> Result { + reject_tags(params, &[Tag::RsaOaepMgfDigest])?; + reject_some_digest(params)?; + let bmode = get_block_mode(params)?; + let padding = get_padding_mode(params)?; + + if bmode == BlockMode::Gcm { + let mac_len = get_tag_value!(params, MacLength, ErrorCode::MissingMacLength)?; + if mac_len % 8 != 0 || mac_len > 128 { + return Err(km_err!(UnsupportedMacLength, "invalid mac len {}", mac_len)); + } + let min_mac_len = get_tag_value!(chars, MinMacLength, ErrorCode::MissingMinMacLength)?; + if mac_len < min_mac_len { + return Err(km_err!( + InvalidMacLength, + "mac len {} less than min {}", + mac_len, + min_mac_len + )); + } + } + match bmode { + BlockMode::Gcm | BlockMode::Ctr => match padding { + PaddingMode::None => {} + _ => { + return Err(km_err!( + IncompatiblePaddingMode, + "padding {:?} not valid for AES GCM/CTR", + padding + )) + } + }, + BlockMode::Ecb | BlockMode::Cbc => match padding { + PaddingMode::None | PaddingMode::Pkcs7 => {} + _ => { + return Err(km_err!( + IncompatiblePaddingMode, + "padding {:?} not valid for AES GCM/CTR", + padding + )) + } + }, + } + + if let Some(nonce) = caller_nonce { + match bmode { + BlockMode::Cbc if nonce.len() == 16 => {} + BlockMode::Ctr if nonce.len() == 16 => {} + BlockMode::Gcm if nonce.len() == 12 => {} + _ => { + return Err(km_err!( + InvalidNonce, + "invalid caller nonce len {} for {:?}", + nonce.len(), + bmode + )) + } + } + } + Ok(BeginParamsToCheck { block_mode: true, padding: true, digest: false, mgf_digest: false }) +} + +/// Check that a 3-DES operation with the given `purpose` and `params` can validly be started +/// using a key with characteristics `chars`. +fn check_begin_3des_params( + params: &[KeyParam], + caller_nonce: Option<&[u8]>, +) -> Result { + reject_tags(params, &[Tag::RsaOaepMgfDigest])?; + reject_some_digest(params)?; + let bmode = get_block_mode(params)?; + let _padding = get_padding_mode(params)?; + + match bmode { + BlockMode::Cbc | BlockMode::Ecb => {} + _ => { + return Err(km_err!(UnsupportedBlockMode, "block mode {:?} not valid for 3-DES", bmode)) + } + } + + if let Some(nonce) = caller_nonce { + match bmode { + BlockMode::Cbc if nonce.len() == 8 => {} + _ => { + return Err(km_err!( + InvalidNonce, + "invalid caller nonce len {} for {:?}", + nonce.len(), + bmode + )) + } + } + } + Ok(BeginParamsToCheck { block_mode: true, padding: true, digest: false, mgf_digest: false }) +} + +/// Check that an HMAC operation with the given `purpose` and `params` can validly be started +/// using a key with characteristics `chars`. +fn check_begin_hmac_params( + chars: &[KeyParam], + purpose: KeyPurpose, + params: &[KeyParam], +) -> Result { + reject_tags(params, &[Tag::BlockMode, Tag::RsaOaepMgfDigest])?; + reject_some_padding(params)?; + let digest = get_digest(params)?; + if purpose == KeyPurpose::Sign { + let mac_len = get_tag_value!(params, MacLength, ErrorCode::MissingMacLength)?; + if mac_len % 8 != 0 || mac_len > digest_len(digest)? { + return Err(km_err!(UnsupportedMacLength, "invalid mac len {}", mac_len)); + } + let min_mac_len = get_tag_value!(chars, MinMacLength, ErrorCode::MissingMinMacLength)?; + if mac_len < min_mac_len { + return Err(km_err!( + InvalidMacLength, + "mac len {} less than min {}", + mac_len, + min_mac_len + )); + } + } + + Ok(BeginParamsToCheck { block_mode: false, padding: false, digest: true, mgf_digest: false }) +} + +/// Return the length in bits of a [`Digest`] function. +pub fn digest_len(digest: Digest) -> Result { + match digest { + Digest::Md5 => Ok(128), + Digest::Sha1 => Ok(160), + Digest::Sha224 => Ok(224), + Digest::Sha256 => Ok(256), + Digest::Sha384 => Ok(384), + Digest::Sha512 => Ok(512), + _ => Err(km_err!(IncompatibleDigest, "invalid digest {:?}", digest)), + } +} + +/// Check the required key params for an RSA wrapping key used in secure import and return the +/// [`DecryptionMode`] constructed from the processed key characteristics. +pub fn check_rsa_wrapping_key_params( + chars: &[KeyParam], + params: &[KeyParam], +) -> Result { + // Check the purpose of the wrapping key + if !contains_tag_value!(chars, Purpose, KeyPurpose::WrapKey) { + return Err(km_err!(IncompatiblePurpose, "no wrap key purpose for the wrapping key")); + } + let padding_mode = get_tag_value!(params, Padding, ErrorCode::IncompatiblePaddingMode)?; + if padding_mode != PaddingMode::RsaOaep { + return Err(km_err!( + IncompatiblePaddingMode, + "invalid padding mode {:?} for RSA wrapping key", + padding_mode + )); + } + let msg_digest = get_tag_value!(params, Digest, ErrorCode::IncompatibleDigest)?; + if msg_digest != Digest::Sha256 { + return Err(km_err!( + IncompatibleDigest, + "invalid digest {:?} for RSA wrapping key", + padding_mode + )); + } + let opt_mgf_digest = get_opt_tag_value!(params, RsaOaepMgfDigest)?; + if opt_mgf_digest == Some(&Digest::None) { + return Err(km_err!(UnsupportedMgfDigest, "MGF digest cannot be NONE for RSA-OAEP")); + } + + if !contains_tag_value!(chars, Padding, padding_mode) { + return Err(km_err!( + IncompatiblePaddingMode, + "padding mode {:?} not in key characteristics {:?}", + padding_mode, + chars, + )); + } + if !contains_tag_value!(chars, Digest, msg_digest) { + return Err(km_err!( + IncompatibleDigest, + "digest {:?} not in key characteristics {:?}", + msg_digest, + chars, + )); + } + + if let Some(mgf_digest) = opt_mgf_digest { + // MGF digest explicitly specified, check it is in key characteristics. + if !contains_tag_value!(chars, RsaOaepMgfDigest, *mgf_digest) { + return Err(km_err!( + IncompatibleDigest, + "MGF digest {:?} not in key characteristics {:?}", + mgf_digest, + chars, + )); + } + } + let mgf_digest = opt_mgf_digest.unwrap_or(&Digest::Sha1); + + let rsa_oaep_decrypt_mode = DecryptionMode::OaepPadding { msg_digest, mgf_digest: *mgf_digest }; + Ok(rsa_oaep_decrypt_mode) +} + +/// Calculate the [Luhn checksum](https://en.wikipedia.org/wiki/Luhn_algorithm) of the given number. +fn luhn_checksum(mut val: u64) -> u64 { + let mut ii = 0; + let mut sum_digits = 0; + while val != 0 { + let curr_digit = val % 10; + let multiplier = if ii % 2 == 0 { 2 } else { 1 }; + let digit_multiplied = curr_digit * multiplier; + sum_digits += (digit_multiplied % 10) + (digit_multiplied / 10); + val /= 10; + ii += 1; + } + (10 - (sum_digits % 10)) % 10 +} + +/// Derive an IMEI value from a first IMEI value, by incrementing by one and re-calculating +/// the Luhn checksum. Return an empty vector on any failure. +pub fn increment_imei(imei: &[u8]) -> Vec { + if imei.is_empty() { + info!("empty IMEI"); + return Vec::new(); + } + + // Expect ASCII digits. + let imei: &str = match core::str::from_utf8(imei) { + Ok(v) => v, + Err(_) => { + warn!("IMEI is not UTF-8"); + return Vec::new(); + } + }; + let imei: u64 = match imei.parse() { + Ok(v) => v, + Err(_) => { + warn!("IMEI is not numeric"); + return Vec::new(); + } + }; + + // Drop trailing checksum digit, increment, and restore checksum. + let imei2 = (imei / 10) + 1; + let imei2 = (imei2 * 10) + luhn_checksum(imei2); + + // Convert back to bytes. + std::format!("{}", imei2).into_bytes() +} diff --git a/libs/rust/common/src/tag/info.rs b/libs/rust/common/src/tag/info.rs new file mode 100644 index 0000000..ad5405d --- /dev/null +++ b/libs/rust/common/src/tag/info.rs @@ -0,0 +1,1188 @@ +// Copyright 2022, 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. + +//! Static information about tag behaviour. + +use crate::{km_err, Error}; +use kmr_wire::keymint::{Tag, TagType}; + +#[cfg(test)] +mod tests; + +/// Indicate the allowed use of the tag as a key characteristic. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Characteristic { + /// Tag is a key characteristic that is enforced by KeyMint (at whatever security + /// level the KeyMint implementation is running at), and is visible to KeyMint + /// users (e.g. via GetKeyCharacteristics). + KeyMintEnforced, + + /// Tag is a key characteristic that is enforced by KeyMint (at whatever security + /// level the KeyMint implementation is running at), but is not exposed to KeyMint + /// users. If a key has this tag associated with it, all operations on the key + /// must have this tag provided as an operation parameter. + KeyMintHidden, + + /// Tag is a key characteristic that is enforced by Keystore. + KeystoreEnforced, + + /// Tag is enforced by both KeyMint and Keystore, in different ways. + BothEnforced, + + /// Tag is not a key characteristic, either because it only acts as an operation + /// parameter or because it never appears on the API. + NotKeyCharacteristic, +} + +/// The set of characteristics that are necessarily enforced by Keystore. +pub const KEYSTORE_ENFORCED_CHARACTERISTICS: &[Tag] = &[ + Tag::ActiveDatetime, + Tag::OriginationExpireDatetime, + Tag::UsageExpireDatetime, + Tag::UserId, + Tag::AllowWhileOnBody, + Tag::CreationDatetime, + Tag::MaxBootLevel, + Tag::UnlockedDeviceRequired, +]; + +/// The set of characteristics that are enforced by KeyMint. +pub const KEYMINT_ENFORCED_CHARACTERISTICS: &[Tag] = &[ + Tag::UserSecureId, + Tag::Algorithm, + Tag::EcCurve, + Tag::UserAuthType, + Tag::Origin, + Tag::Purpose, + Tag::BlockMode, + Tag::Digest, + Tag::Padding, + Tag::RsaOaepMgfDigest, + Tag::KeySize, + Tag::MinMacLength, + Tag::MaxUsesPerBoot, + Tag::AuthTimeout, + Tag::OsVersion, + Tag::OsPatchlevel, + Tag::VendorPatchlevel, + Tag::BootPatchlevel, + Tag::RsaPublicExponent, + Tag::CallerNonce, + Tag::BootloaderOnly, + Tag::RollbackResistance, + Tag::EarlyBootOnly, + Tag::NoAuthRequired, + Tag::TrustedUserPresenceRequired, + Tag::TrustedConfirmationRequired, + Tag::StorageKey, +]; + +/// The set of characteristics that are automatically added by KeyMint on key generation. +pub const AUTO_ADDED_CHARACTERISTICS: &[Tag] = + &[Tag::Origin, Tag::OsVersion, Tag::OsPatchlevel, Tag::VendorPatchlevel, Tag::BootPatchlevel]; + +/// Indicate the allowed use of the tag as a parameter for an operation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OperationParam { + /// Tag acts as an operation parameter for key generation/import operations. + KeyGenImport, + + /// Tag is provided as an explicit argument for a cipher operation, and must + /// match one of the values for this tag in the key characteristics. + CipherExplicitArgOneOf, + + /// Tag is provided as a parameter for a cipher operation, and must + /// match one of the values for this tag in the key characteristics. + CipherParamOneOf, + + /// Tag is provided as a parameter for a cipher operation, and must + /// exactly match the (single) value for this tag in the key characteristics. + CipherParamExactMatch, + + /// Tag is provided as a parameter for a cipher operation, and is not a key + /// characteristic. + CipherParam, + + /// Tag is not an operation parameter; this *normally* means that it only acts + /// as a key characteristic (exception: ROOT_OF_TRUST is neither an operation + /// parameter nor a key characteristic). + NotOperationParam, +} + +/// Indicate whether the KeyMint user is allowed to specify this tag. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct UserSpecifiable(pub bool); + +/// Indicate whether the KeyMint implementation auto-adds this tag as a characteristic to generated +/// or imported keys. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AutoAddedCharacteristic(pub bool); + +/// Indicate the lifetime of the value associated with the tag. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ValueLifetime { + /// Indicates that the value of the tag is communicated to KeyMint from the bootloader, and + /// fixed thereafter. + FixedAtBoot, + /// Indicates that the value of the tag is communicated to KeyMint from the HAL service, and + /// fixed thereafter. + FixedAtStartup, + /// Indicates that the value of the tag varies from key to key, or operation to operation. + Variable, +} + +/// Indicate whether a tag provided as an asymmetric key generation/import parameter is +/// required for the production of a certificate or attestation extension. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CertGenParam { + /// Tag not required as a parameter for certificate or extension generation (although its value + /// may appear in the extension as a key characteristic). + /// + /// Example: `Tag::KeySize` doesn't affect cert generation (but does appear in any attestation + /// extension). + NotRequired, + /// Tag must be specified as a parameter on key generation in order to get certificate + /// generation. + /// + /// Example: `Tag::CertificateNotBefore` must be specified to get a cert. + Required, + /// Tag must be specified as a parameter on key generation in order to get an attestation + /// extension in a generated certificate. + /// + /// Example: `Tag::AttestationChallenge` must be specified to get a cert with an attestation + /// extension. + RequiredForAttestation, + /// Tag need not be specified as a parameter on key generation, but if specified it does affect + /// the contents of the generated certificate (not extension). + /// + /// Example: `Tag::CertificateSerial` can be omitted, but if supplied it alters the cert. + Optional, + /// Tag need not be specified as a parameter on key generation, but if specified it does affect + /// the contents of the attestation extension. + /// + /// Example: `Tag::ResetSinceIdRotation` can be omitted, but if supplied (along with + /// `Tag::IncludeUniqueId`) then the attestation extension contents are altered. + OptionalForAttestation, + /// Special cases; see individual tags for information. + Special, +} + +/// Information about a tag's behaviour. +#[derive(Debug, Clone)] +pub struct Info { + /// Tag name as a string for debug purposes. + pub name: &'static str, + /// Indication of the type of the corresponding value. + pub tt: TagType, + /// Indicates whether the tag value appears in an attestation extension, and as what ASN.1 + /// type. + pub ext_asn1_type: Option<&'static str>, + /// Indicates whether the KeyMint user can specify this tag. + pub user_can_specify: UserSpecifiable, + /// Indicates how this tag acts as a key characteristic. + pub characteristic: Characteristic, + /// Indicates how this tag acts as an operation parameter. + pub op_param: OperationParam, + /// Indicates whether KeyMint automatically adds this tag to keys as a key characteristic. + pub keymint_auto_adds: AutoAddedCharacteristic, + /// Indicates the lifetime of the value associated with this tag. + pub lifetime: ValueLifetime, + /// Indicates the role this tag plays in certificate generation for asymmetric keys. + pub cert_gen: CertGenParam, + /// Unique bit index for tracking this tag. + bit_index: usize, +} + +/// Global "map" of tags to information about their behaviour. +/// Encoded as an array to avoid allocation; lookup should only be slightly slower +/// for this few entries. +const INFO: [(Tag, Info); 61] = [ + ( + Tag::Purpose, + Info { + name: "PURPOSE", + tt: TagType::EnumRep, + ext_asn1_type: Some("SET OF INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::CipherExplicitArgOneOf, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 0, + }, + ), + ( + Tag::Algorithm, + Info { + name: "ALGORITHM", + tt: TagType::Enum, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 1, + }, + ), + ( + Tag::KeySize, + Info { + name: "KEY_SIZE", + tt: TagType::Uint, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 2, + }, + ), + ( + Tag::BlockMode, + Info { + name: "BLOCK_MODE", + tt: TagType::EnumRep, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::CipherParamOneOf, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 3, + }, + ), + ( + Tag::Digest, + Info { + name: "DIGEST", + tt: TagType::EnumRep, + ext_asn1_type: Some("SET OF INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::CipherParamOneOf, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 4, + }, + ), + ( + Tag::Padding, + Info { + name: "PADDING", + tt: TagType::EnumRep, + ext_asn1_type: Some("SET OF INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::CipherParamOneOf, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 5, + }, + ), + ( + Tag::CallerNonce, + Info { + name: "CALLER_NONCE", + tt: TagType::Bool, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 6, + }, + ), + ( + Tag::MinMacLength, + Info { + name: "MIN_MAC_LENGTH", + tt: TagType::Uint, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 7, + }, + ), + ( + Tag::EcCurve, + Info { + name: "EC_CURVE", + tt: TagType::Enum, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 8, + }, + ), + ( + Tag::RsaPublicExponent, + Info { + name: "RSA_PUBLIC_EXPONENT", + tt: TagType::Ulong, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 9, + }, + ), + ( + Tag::IncludeUniqueId, + Info { + name: "INCLUDE_UNIQUE_ID", + tt: TagType::Bool, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 10, + }, + ), + ( + Tag::RsaOaepMgfDigest, + Info { + name: "RSA_OAEP_MGF_DIGEST", + tt: TagType::EnumRep, + ext_asn1_type: Some("SET OF INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::CipherParamOneOf, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 11, + }, + ), + ( + Tag::BootloaderOnly, + Info { + name: "BOOTLOADER_ONLY", + tt: TagType::Bool, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(false), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 12, + }, + ), + ( + Tag::RollbackResistance, + Info { + name: "ROLLBACK_RESISTANCE", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 13, + }, + ), + ( + Tag::EarlyBootOnly, + Info { + name: "EARLY_BOOT_ONLY", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 14, + }, + ), + ( + Tag::ActiveDatetime, + Info { + name: "ACTIVE_DATETIME", + tt: TagType::Date, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 15, + }, + ), + ( + Tag::OriginationExpireDatetime, + Info { + name: "ORIGINATION_EXPIRE_DATETIME", + tt: TagType::Date, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 16, + }, + ), + ( + Tag::UsageExpireDatetime, + Info { + name: "USAGE_EXPIRE_DATETIME", + tt: TagType::Date, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 17, + }, + ), + ( + Tag::MaxUsesPerBoot, + Info { + name: "MAX_USES_PER_BOOT", + tt: TagType::Uint, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 18, + }, + ), + ( + Tag::UsageCountLimit, + Info { + name: "USAGE_COUNT_LIMIT", + tt: TagType::Uint, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::BothEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 19, + }, + ), + ( + Tag::UserId, + Info { + name: "USER_ID", + tt: TagType::Uint, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 20, + }, + ), + // Value must match userID or secureId in authToken param + ( + Tag::UserSecureId, + Info { + name: "USER_SECURE_ID", + tt: TagType::UlongRep, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::CipherExplicitArgOneOf, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 21, + }, + ), + ( + Tag::NoAuthRequired, + Info { + name: "NO_AUTH_REQUIRED", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 22, + }, + ), + ( + Tag::UserAuthType, + Info { + name: "USER_AUTH_TYPE", + tt: TagType::Enum, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::CipherParamOneOf, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 23, + }, + ), + ( + Tag::AuthTimeout, + Info { + name: "AUTH_TIMEOUT", + tt: TagType::Uint, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 24, + }, + ), + ( + Tag::AllowWhileOnBody, + Info { + name: "ALLOW_WHILE_ON_BODY", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 25, + }, + ), + ( + Tag::TrustedUserPresenceRequired, + Info { + name: "TRUSTED_USER_PRESENCE_REQUIRED", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 26, + }, + ), + ( + Tag::TrustedConfirmationRequired, + Info { + name: "TRUSTED_CONFIRMATION_REQUIRED", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 27, + }, + ), + ( + Tag::UnlockedDeviceRequired, + Info { + name: "UNLOCKED_DEVICE_REQUIRED", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 28, + }, + ), + ( + Tag::ApplicationId, + Info { + name: "APPLICATION_ID", + tt: TagType::Bytes, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintHidden, + op_param: OperationParam::CipherParamExactMatch, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 29, + }, + ), + ( + Tag::ApplicationData, + Info { + name: "APPLICATION_DATA", + tt: TagType::Bytes, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintHidden, + op_param: OperationParam::CipherParamExactMatch, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 30, + }, + ), + ( + Tag::CreationDatetime, + Info { + name: "CREATION_DATETIME", + tt: TagType::Date, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + // If `Tag::IncludeUniqueId` is specified for attestation extension + // generation, then a value for `Tag::CreationDatetime` is needed for + // the calculation of the unique ID value. + cert_gen: CertGenParam::Special, + bit_index: 31, + }, + ), + ( + Tag::Origin, + Info { + name: "ORIGIN", + tt: TagType::Enum, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(false), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(true), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 32, + }, + ), + ( + Tag::RootOfTrust, + Info { + name: "ROOT_OF_TRUST", + tt: TagType::Bytes, + ext_asn1_type: Some("RootOfTrust SEQUENCE"), + user_can_specify: UserSpecifiable(false), + // The root of trust is neither a key characteristic nor an operation parameter. + // The tag exists only to reserve a numeric value that can be used in the + // attestation extension record. + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::NotRequired, + bit_index: 33, + }, + ), + ( + Tag::OsVersion, + Info { + name: "OS_VERSION", + tt: TagType::Uint, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(false), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(true), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::NotRequired, + bit_index: 34, + }, + ), + ( + Tag::OsPatchlevel, + Info { + name: "OS_PATCHLEVEL", + tt: TagType::Uint, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(false), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(true), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::NotRequired, + bit_index: 35, + }, + ), + ( + Tag::UniqueId, + Info { + name: "UNIQUE_ID", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(false), + // The unique ID is neither a key characteristic nor an operation parameter. + // + // The docs claim that tag exists only to reserve a numeric value that can be used in + // the attestation extension record created on key generation. + // + // However, the unique ID gets a field of its own in the top-level KeyDescription + // SEQUENCE; it does not appear in the AuthorizationList SEQUENCE, so this tag value + // should never be seen anywhere. + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::Special, + bit_index: 36, + }, + ), + ( + Tag::AttestationChallenge, + Info { + name: "ATTESTATION_CHALLENGE", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::RequiredForAttestation, + bit_index: 37, + }, + ), + ( + Tag::AttestationApplicationId, + Info { + name: "ATTESTATION_APPLICATION_ID", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::RequiredForAttestation, + bit_index: 38, + }, + ), + ( + Tag::AttestationIdBrand, + Info { + name: "ATTESTATION_ID_BRAND", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 39, + }, + ), + ( + Tag::AttestationIdDevice, + Info { + name: "ATTESTATION_ID_DEVICE", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 40, + }, + ), + ( + Tag::AttestationIdProduct, + Info { + name: "ATTESTATION_ID_PRODUCT", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 41, + }, + ), + ( + Tag::AttestationIdSerial, + Info { + name: "ATTESTATION_ID_SERIAL", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 42, + }, + ), + ( + Tag::AttestationIdImei, + Info { + name: "ATTESTATION_ID_IMEI", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 43, + }, + ), + ( + Tag::AttestationIdSecondImei, + Info { + name: "ATTESTATION_ID_SECOND_IMEI", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 44, + }, + ), + ( + Tag::AttestationIdMeid, + Info { + name: "ATTESTATION_ID_MEID", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 45, + }, + ), + ( + Tag::AttestationIdManufacturer, + Info { + name: "ATTESTATION_ID_MANUFACTURER", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 46, + }, + ), + ( + Tag::AttestationIdModel, + Info { + name: "ATTESTATION_ID_MODEL", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 47, + }, + ), + ( + Tag::VendorPatchlevel, + Info { + name: "VENDOR_PATCHLEVEL", + tt: TagType::Uint, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(false), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(true), + lifetime: ValueLifetime::FixedAtStartup, + cert_gen: CertGenParam::NotRequired, + bit_index: 48, + }, + ), + ( + Tag::BootPatchlevel, + Info { + name: "BOOT_PATCHLEVEL", + tt: TagType::Uint, + ext_asn1_type: Some("INTEGER"), + user_can_specify: UserSpecifiable(false), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(true), + lifetime: ValueLifetime::FixedAtBoot, + cert_gen: CertGenParam::NotRequired, + bit_index: 49, + }, + ), + ( + Tag::DeviceUniqueAttestation, + Info { + name: "DEVICE_UNIQUE_ATTESTATION", + tt: TagType::Bool, + ext_asn1_type: Some("NULL"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + // Device unique attestation does not affect the contents of the `tbsCertificate`, + // but it does change the chain used to sign the resulting certificate. + cert_gen: CertGenParam::Special, + bit_index: 50, + }, + ), + // A key marked as a storage key cannot be used via most of the KeyMint API. Instead, it + // can be passed to `convertStorageKeyToEphemeral` to convert it to an ephemeral key. + ( + Tag::StorageKey, + Info { + name: "STORAGE_KEY", + tt: TagType::Bool, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeyMintEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 51, + }, + ), + // Can only be user-specified if CALLER_NONCE set in key characteristics. + ( + Tag::Nonce, + Info { + name: "NONCE", + tt: TagType::Bytes, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::CipherParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 52, + }, + ), + ( + Tag::MacLength, + Info { + name: "MAC_LENGTH", + tt: TagType::Uint, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::CipherParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 53, + }, + ), + ( + Tag::ResetSinceIdRotation, + Info { + name: "RESET_SINCE_ID_ROTATION", + tt: TagType::Bool, + ext_asn1_type: Some("part of UniqueID"), + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::OptionalForAttestation, + bit_index: 54, + }, + ), + // Default to 1 if not present + ( + Tag::CertificateSerial, + Info { + name: "CERTIFICATE_SERIAL", + tt: TagType::Bignum, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::Optional, + bit_index: 55, + }, + ), + // Default to "CN=Android Keystore Key" if not present + ( + Tag::CertificateSubject, + Info { + name: "CERTIFICATE_SUBJECT", + tt: TagType::Bytes, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::Optional, + bit_index: 56, + }, + ), + ( + Tag::CertificateNotBefore, + Info { + name: "CERTIFICATE_NOT_BEFORE", + tt: TagType::Date, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::Required, + bit_index: 57, + }, + ), + ( + Tag::CertificateNotAfter, + Info { + name: "CERTIFICATE_NOT_AFTER", + tt: TagType::Date, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::KeyGenImport, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::Required, + bit_index: 58, + }, + ), + ( + Tag::MaxBootLevel, + Info { + name: "MAX_BOOT_LEVEL", + tt: TagType::Uint, + ext_asn1_type: None, + user_can_specify: UserSpecifiable(true), + characteristic: Characteristic::KeystoreEnforced, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::Variable, + cert_gen: CertGenParam::NotRequired, + bit_index: 59, + }, + ), + ( + Tag::ModuleHash, + Info { + name: "MODULE_HASH", + tt: TagType::Bytes, + ext_asn1_type: Some("OCTET STRING"), + user_can_specify: UserSpecifiable(false), + // The module hash is neither a key characteristic nor an operation parameter. + // The tag exists only to reserve a numeric value that can be used in the + // attestation extension record. + characteristic: Characteristic::NotKeyCharacteristic, + op_param: OperationParam::NotOperationParam, + keymint_auto_adds: AutoAddedCharacteristic(false), + lifetime: ValueLifetime::FixedAtStartup, + cert_gen: CertGenParam::NotRequired, + bit_index: 60, + }, + ), +]; + +/// Return behaviour information about the specified tag. +pub fn info(tag: Tag) -> Result<&'static Info, Error> { + for (t, info) in &INFO { + if tag == *t { + return Ok(info); + } + } + Err(km_err!(InvalidTag, "unknown tag {:?}", tag)) +} + +/// Indicate whether a tag is allowed to have multiple values. +#[inline] +pub fn multivalued(tag: Tag) -> bool { + matches!( + kmr_wire::keymint::tag_type(tag), + TagType::EnumRep | TagType::UintRep | TagType::UlongRep + ) +} + +/// Tracker for observed tag values. +#[derive(Default)] +pub struct DuplicateTagChecker(u64); + +impl DuplicateTagChecker { + /// Add the given tag to the set of seen tags, failing if the tag + /// has already been observed (and is not multivalued). + pub fn add(&mut self, tag: Tag) -> Result<(), Error> { + let bit_idx = info(tag)?.bit_index; + let bit_mask = 0x01u64 << bit_idx; + if !multivalued(tag) && (self.0 & bit_mask) != 0 { + return Err(km_err!(InvalidKeyBlob, "duplicate value for {:?}", tag)); + } + self.0 |= bit_mask; + Ok(()) + } +} diff --git a/libs/rust/common/src/tag/info/tests.rs b/libs/rust/common/src/tag/info/tests.rs new file mode 100644 index 0000000..dd0d07e --- /dev/null +++ b/libs/rust/common/src/tag/info/tests.rs @@ -0,0 +1,93 @@ +// Copyright 2022, 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. + +use std::collections::BTreeSet; + +use super::*; + +#[test] +fn test_auto_added_const() { + let mut want = BTreeSet::new(); + for (tag, info) in INFO.iter() { + if info.keymint_auto_adds.0 { + want.insert(*tag); + } + } + let got: BTreeSet = AUTO_ADDED_CHARACTERISTICS.iter().cloned().collect(); + assert_eq!(want, got, "AUTO_ADDED_CHARACTERISTICS constant doesn't match INFO contents"); +} + +#[test] +fn test_keystore_enforced_const() { + let mut want = BTreeSet::new(); + for (tag, info) in INFO.iter() { + if info.characteristic == Characteristic::KeystoreEnforced { + want.insert(*tag); + } + } + let got: BTreeSet = KEYSTORE_ENFORCED_CHARACTERISTICS.iter().cloned().collect(); + assert_eq!(want, got, "KEYSTORE_ENFORCED_CHARACTERISTICS constant doesn't match INFO contents"); +} + +#[test] +fn test_keymint_enforced_const() { + let mut want = BTreeSet::new(); + for (tag, info) in INFO.iter() { + if info.characteristic == Characteristic::KeyMintEnforced { + want.insert(*tag); + } + } + let got: BTreeSet = KEYMINT_ENFORCED_CHARACTERISTICS.iter().cloned().collect(); + assert_eq!(want, got, "KEYMINT_ENFORCED_CHARACTERISTICS constant doesn't match INFO contents"); +} + +#[test] +fn test_tag_bit_index_unique() { + let mut seen = std::collections::BTreeSet::new(); + for (tag, info) in INFO.iter() { + assert!( + !seen.contains(&info.bit_index), + "Duplicate bit index {} for {:?}", + info.bit_index, + tag + ); + seen.insert(info.bit_index); + // Bitwise tag tracking currently assumes they will all fit in `u64` + assert!(info.bit_index < 64); + } +} + +#[test] +fn test_tag_tracker() { + let tests = vec![ + (true, vec![Tag::BlockMode, Tag::Padding]), + (true, vec![Tag::BlockMode, Tag::Padding, Tag::BlockMode]), + (true, vec![Tag::BlockMode, Tag::EcCurve]), + (true, vec![Tag::BlockMode, Tag::EcCurve, Tag::BlockMode]), + (false, vec![Tag::BlockMode, Tag::EcCurve, Tag::BlockMode, Tag::EcCurve]), + ]; + for (valid, list) in tests { + let mut tracker = DuplicateTagChecker::default(); + let (most, last) = list.split_at(list.len() - 1); + for tag in most { + tracker.add(*tag).unwrap(); + } + let result = tracker.add(last[0]); + if valid { + assert!(result.is_ok()); + } else { + assert!(result.is_err()); + } + } +} diff --git a/libs/rust/common/src/tag/legacy.rs b/libs/rust/common/src/tag/legacy.rs new file mode 100644 index 0000000..c0c484e --- /dev/null +++ b/libs/rust/common/src/tag/legacy.rs @@ -0,0 +1,600 @@ +// Copyright 2022, 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. + +//! Helper functionality for working with legacy tag serialization. + +use crate::{km_err, try_to_vec, vec_try, vec_try_with_capacity, Error, FallibleAllocExt}; +use core::cmp::Ordering; +use core::convert::{TryFrom, TryInto}; +use kmr_wire::{ + keymint::{ + Algorithm, BlockMode, DateTime, Digest, EcCurve, KeyOrigin, KeyParam, KeyPurpose, + PaddingMode, Tag, + }, + KeySizeInBits, RsaExponent, +}; + +/// Retrieve a `u8` from the start of the given slice, if possible. +pub fn consume_u8(data: &mut &[u8]) -> Result { + match data.first() { + Some(b) => { + *data = &(*data)[1..]; + Ok(*b) + } + None => Err(km_err!(InvalidKeyBlob, "failed to find 1 byte")), + } +} + +/// Move past a bool value from the start of the given slice, if possible. +/// Bool values should only be included if `true`, so fail if the value +/// is anything other than 1. +pub fn consume_bool(data: &mut &[u8]) -> Result<(), Error> { + let b = consume_u8(data)?; + if b == 0x01 { + Ok(()) + } else { + Err(km_err!(InvalidKeyBlob, "bool value other than 1 encountered")) + } +} + +/// Retrieve a (host-ordered) `u32` from the start of the given slice, if possible. +pub fn consume_u32(data: &mut &[u8]) -> Result { + if data.len() < 4 { + return Err(km_err!(InvalidKeyBlob, "failed to find 4 bytes")); + } + let chunk: [u8; 4] = data[..4].try_into().unwrap(); // safe: just checked + *data = &(*data)[4..]; + Ok(u32::from_ne_bytes(chunk)) +} + +/// Retrieve a (host-ordered) `i32` from the start of the given slice, if possible. +pub fn consume_i32(data: &mut &[u8]) -> Result { + if data.len() < 4 { + return Err(km_err!(InvalidKeyBlob, "failed to find 4 bytes")); + } + let chunk: [u8; 4] = data[..4].try_into().unwrap(); // safe: just checked + *data = &(*data)[4..]; + Ok(i32::from_ne_bytes(chunk)) +} + +/// Retrieve a (host-ordered) `u64` from the start of the given slice, if possible. +pub fn consume_u64(data: &mut &[u8]) -> Result { + if data.len() < 8 { + return Err(km_err!(InvalidKeyBlob, "failed to find 8 bytes")); + } + let chunk: [u8; 8] = data[..8].try_into().unwrap(); // safe: just checked + *data = &(*data)[8..]; + Ok(u64::from_ne_bytes(chunk)) +} + +/// Retrieve a (host-ordered) `i64` from the start of the given slice, if possible. +pub fn consume_i64(data: &mut &[u8]) -> Result { + if data.len() < 8 { + return Err(km_err!(InvalidKeyBlob, "failed to find 8 bytes")); + } + let chunk: [u8; 8] = data[..8].try_into().unwrap(); // safe: just checked + *data = &(*data)[8..]; + Ok(i64::from_ne_bytes(chunk)) +} + +/// Retrieve a vector of bytes from the start of the given slice, if possible, +/// with the length of the data is expected to appear as a host-ordered `u32` prefix. +pub fn consume_vec(data: &mut &[u8]) -> Result, Error> { + let len = consume_u32(data)? as usize; + if len > data.len() { + return Err(km_err!(InvalidKeyBlob, "failed to find {} bytes", len)); + } + let result = try_to_vec(&data[..len])?; + *data = &(*data)[len..]; + Ok(result) +} + +/// Serialize a collection of [`KeyParam`]s into a format that is compatible with previous +/// implementations: +/// +/// ```text +/// [0..4] Size B of `TagType::Bytes` data, in host order. +/// [4..4+B] (*) Concatenated contents of each `TagType::Bytes` tag. +/// [4+B..4+B+4] Count N of the number of parameters, in host order. +/// [8+B..8+B+4] Size Z of encoded parameters. +/// [12+B..12+B+Z] Serialized parameters one after another. +/// ``` +/// +/// Individual parameters are serialized in the last chunk as: +/// +/// ```text +/// [0..4] Tag number, in host order. +/// Followed by one of the following depending on the tag's `TagType`; all integers in host order: +/// [4..5] Bool value (`TagType::Bool`) +/// [4..8] i32 values (`TagType::Uint[Rep]`, `TagType::Enum[Rep]`) +/// [4..12] i64 values, in host order (`TagType::UlongRep`, `TagType::Date`) +/// [4..8] + [8..12] Size + offset of data in (*) above (`TagType::Bytes`, `TagType::Bignum`) +/// ``` +pub fn serialize(params: &[KeyParam]) -> Result, Error> { + // First 4 bytes are the length of the combined [`TagType::Bytes`] data; come back to set that + // in a moment. + let mut result = vec_try![0; 4]?; + + // Next append the contents of all of the [`TagType::Bytes`] data. + let mut blob_size = 0u32; + for param in params { + match param { + // Variants that hold `Vec`. + KeyParam::ApplicationId(v) + | KeyParam::ApplicationData(v) + | KeyParam::AttestationChallenge(v) + | KeyParam::AttestationApplicationId(v) + | KeyParam::AttestationIdBrand(v) + | KeyParam::AttestationIdDevice(v) + | KeyParam::AttestationIdProduct(v) + | KeyParam::AttestationIdSerial(v) + | KeyParam::AttestationIdImei(v) + | KeyParam::AttestationIdSecondImei(v) + | KeyParam::AttestationIdMeid(v) + | KeyParam::AttestationIdManufacturer(v) + | KeyParam::AttestationIdModel(v) + | KeyParam::Nonce(v) + | KeyParam::RootOfTrust(v) + | KeyParam::CertificateSerial(v) + | KeyParam::CertificateSubject(v) + | KeyParam::ModuleHash(v) => { + result.try_extend_from_slice(v)?; + blob_size += v.len() as u32; + } + _ => {} + } + } + // Go back and fill in the combined blob length in native order at the start. + result[..4].clone_from_slice(&blob_size.to_ne_bytes()); + + result.try_extend_from_slice(&(params.len() as u32).to_ne_bytes())?; + + let params_size_offset = result.len(); + result.try_extend_from_slice(&[0u8; 4])?; // placeholder for size of elements + let first_param_offset = result.len(); + let mut blob_offset = 0u32; + for param in params { + result.try_extend_from_slice(&(param.tag() as u32).to_ne_bytes())?; + match ¶m { + // Enum-holding variants. + KeyParam::Purpose(v) => result.try_extend_from_slice(&(*v as u32).to_ne_bytes())?, + KeyParam::Algorithm(v) => result.try_extend_from_slice(&(*v as u32).to_ne_bytes())?, + KeyParam::BlockMode(v) => result.try_extend_from_slice(&(*v as u32).to_ne_bytes())?, + KeyParam::Digest(v) => result.try_extend_from_slice(&(*v as u32).to_ne_bytes())?, + KeyParam::Padding(v) => result.try_extend_from_slice(&(*v as u32).to_ne_bytes())?, + KeyParam::EcCurve(v) => result.try_extend_from_slice(&(*v as u32).to_ne_bytes())?, + KeyParam::RsaOaepMgfDigest(v) => { + result.try_extend_from_slice(&(*v as u32).to_ne_bytes())? + } + KeyParam::Origin(v) => result.try_extend_from_slice(&(*v as u32).to_ne_bytes())?, + + // `u32`-holding variants. + KeyParam::KeySize(v) => result.try_extend_from_slice(&(v.0).to_ne_bytes())?, + KeyParam::MinMacLength(v) + | KeyParam::MaxUsesPerBoot(v) + | KeyParam::UsageCountLimit(v) + | KeyParam::UserId(v) + | KeyParam::UserAuthType(v) + | KeyParam::AuthTimeout(v) + | KeyParam::OsVersion(v) + | KeyParam::OsPatchlevel(v) + | KeyParam::VendorPatchlevel(v) + | KeyParam::BootPatchlevel(v) + | KeyParam::MacLength(v) + | KeyParam::MaxBootLevel(v) => result.try_extend_from_slice(&v.to_ne_bytes())?, + + // `u64`-holding variants. + KeyParam::RsaPublicExponent(v) => result.try_extend_from_slice(&(v.0).to_ne_bytes())?, + KeyParam::UserSecureId(v) => result.try_extend_from_slice(&(*v).to_ne_bytes())?, + + // `true`-holding variants. + KeyParam::CallerNonce + | KeyParam::IncludeUniqueId + | KeyParam::BootloaderOnly + | KeyParam::RollbackResistance + | KeyParam::EarlyBootOnly + | KeyParam::AllowWhileOnBody + | KeyParam::NoAuthRequired + | KeyParam::TrustedUserPresenceRequired + | KeyParam::TrustedConfirmationRequired + | KeyParam::UnlockedDeviceRequired + | KeyParam::DeviceUniqueAttestation + | KeyParam::StorageKey + | KeyParam::ResetSinceIdRotation => result.try_push(0x01u8)?, + + // `DateTime`-holding variants. + KeyParam::ActiveDatetime(v) + | KeyParam::OriginationExpireDatetime(v) + | KeyParam::UsageExpireDatetime(v) + | KeyParam::CreationDatetime(v) + | KeyParam::CertificateNotBefore(v) + | KeyParam::CertificateNotAfter(v) => { + result.try_extend_from_slice(&(v.ms_since_epoch as u64).to_ne_bytes())? + } + + // `Vec`-holding variants. + KeyParam::ApplicationId(v) + | KeyParam::ApplicationData(v) + | KeyParam::AttestationChallenge(v) + | KeyParam::AttestationApplicationId(v) + | KeyParam::AttestationIdBrand(v) + | KeyParam::AttestationIdDevice(v) + | KeyParam::AttestationIdProduct(v) + | KeyParam::AttestationIdSerial(v) + | KeyParam::AttestationIdImei(v) + | KeyParam::AttestationIdSecondImei(v) + | KeyParam::AttestationIdMeid(v) + | KeyParam::AttestationIdManufacturer(v) + | KeyParam::AttestationIdModel(v) + | KeyParam::Nonce(v) + | KeyParam::RootOfTrust(v) + | KeyParam::CertificateSerial(v) + | KeyParam::CertificateSubject(v) + | KeyParam::ModuleHash(v) => { + let blob_len = v.len() as u32; + result.try_extend_from_slice(&blob_len.to_ne_bytes())?; + result.try_extend_from_slice(&blob_offset.to_ne_bytes())?; + blob_offset += blob_len; + } + } + } + let serialized_size = (result.len() - first_param_offset) as u32; + + // Go back and fill in the total serialized size. + result[params_size_offset..params_size_offset + 4] + .clone_from_slice(&serialized_size.to_ne_bytes()); + Ok(result) +} + +/// Retrieve the contents of a tag of `TagType::Bytes`. The `data` parameter holds +/// the as-yet unparsed data, and a length and offset are read from this (and consumed). +/// This length and offset refer to a location in the combined `blob_data`; however, +/// the offset is expected to be the next unconsumed chunk of `blob_data`, as indicated +/// by `next_blob_offset` (which itself is updated as a result of consuming the data). +fn consume_blob( + data: &mut &[u8], + next_blob_offset: &mut usize, + blob_data: &[u8], +) -> Result, Error> { + let data_len = consume_u32(data)? as usize; + let data_offset = consume_u32(data)? as usize; + // Expect the blob data to come from the next offset in the initial blob chunk. + if data_offset != *next_blob_offset { + return Err(km_err!( + InvalidKeyBlob, + "got blob offset {} instead of {}", + data_offset, + next_blob_offset + )); + } + if (data_offset + data_len) > blob_data.len() { + return Err(km_err!( + InvalidKeyBlob, + "blob at offset [{}..{}+{}] goes beyond blob data size {}", + data_offset, + data_offset, + data_len, + blob_data.len(), + )); + } + + let slice = &blob_data[data_offset..data_offset + data_len]; + *next_blob_offset += data_len; + try_to_vec(slice) +} + +/// Deserialize a collection of [`KeyParam`]s in legacy serialized format. The provided slice is +/// modified to contain the unconsumed part of the data. +pub fn deserialize(data: &mut &[u8]) -> Result, Error> { + let blob_data_size = consume_u32(data)? as usize; + + let blob_data = &data[..blob_data_size]; + let mut next_blob_offset = 0; + + // Move past the blob data. + *data = &data[blob_data_size..]; + + let param_count = consume_u32(data)? as usize; + let param_size = consume_u32(data)? as usize; + if param_size > data.len() { + return Err(km_err!( + InvalidKeyBlob, + "size mismatch 4+{}+4+4+{} > {}", + blob_data_size, + param_size, + data.len() + )); + } + + let mut results = vec_try_with_capacity!(param_count)?; + for _i in 0..param_count { + let tag_num = consume_u32(data)? as i32; + let tag = ::try_from(tag_num) + .map_err(|_e| km_err!(InvalidKeyBlob, "unknown tag {} encountered", tag_num))?; + let enum_err = |_e| km_err!(InvalidKeyBlob, "unknown enum value for {:?}", tag); + results.try_push(match tag { + // Enum-holding variants. + Tag::Purpose => { + KeyParam::Purpose(::try_from(consume_i32(data)?).map_err(enum_err)?) + } + Tag::Algorithm => { + KeyParam::Algorithm(::try_from(consume_i32(data)?).map_err(enum_err)?) + } + Tag::BlockMode => { + KeyParam::BlockMode(::try_from(consume_i32(data)?).map_err(enum_err)?) + } + Tag::Digest => { + KeyParam::Digest(::try_from(consume_i32(data)?).map_err(enum_err)?) + } + Tag::Padding => { + KeyParam::Padding(::try_from(consume_i32(data)?).map_err(enum_err)?) + } + Tag::EcCurve => { + KeyParam::EcCurve(::try_from(consume_i32(data)?).map_err(enum_err)?) + } + Tag::RsaOaepMgfDigest => KeyParam::RsaOaepMgfDigest( + ::try_from(consume_i32(data)?).map_err(enum_err)?, + ), + Tag::Origin => { + KeyParam::Origin(::try_from(consume_i32(data)?).map_err(enum_err)?) + } + + // `u32`-holding variants. + Tag::KeySize => KeyParam::KeySize(KeySizeInBits(consume_u32(data)?)), + Tag::MinMacLength => KeyParam::MinMacLength(consume_u32(data)?), + Tag::MaxUsesPerBoot => KeyParam::MaxUsesPerBoot(consume_u32(data)?), + Tag::UsageCountLimit => KeyParam::UsageCountLimit(consume_u32(data)?), + Tag::UserId => KeyParam::UserId(consume_u32(data)?), + Tag::UserAuthType => KeyParam::UserAuthType(consume_u32(data)?), + Tag::AuthTimeout => KeyParam::AuthTimeout(consume_u32(data)?), + Tag::OsVersion => KeyParam::OsVersion(consume_u32(data)?), + Tag::OsPatchlevel => KeyParam::OsPatchlevel(consume_u32(data)?), + Tag::VendorPatchlevel => KeyParam::VendorPatchlevel(consume_u32(data)?), + Tag::BootPatchlevel => KeyParam::BootPatchlevel(consume_u32(data)?), + Tag::MacLength => KeyParam::MacLength(consume_u32(data)?), + Tag::MaxBootLevel => KeyParam::MaxBootLevel(consume_u32(data)?), + + // `u64`-holding variants. + Tag::RsaPublicExponent => KeyParam::RsaPublicExponent(RsaExponent(consume_u64(data)?)), + Tag::UserSecureId => KeyParam::UserSecureId(consume_u64(data)?), + + // `true`-holding variants. + Tag::CallerNonce => { + consume_bool(data)?; + KeyParam::CallerNonce + } + Tag::IncludeUniqueId => { + consume_bool(data)?; + KeyParam::IncludeUniqueId + } + Tag::BootloaderOnly => { + consume_bool(data)?; + KeyParam::BootloaderOnly + } + Tag::RollbackResistance => { + consume_bool(data)?; + KeyParam::RollbackResistance + } + Tag::EarlyBootOnly => { + consume_bool(data)?; + KeyParam::EarlyBootOnly + } + Tag::AllowWhileOnBody => { + consume_bool(data)?; + KeyParam::AllowWhileOnBody + } + Tag::NoAuthRequired => { + consume_bool(data)?; + KeyParam::NoAuthRequired + } + Tag::TrustedUserPresenceRequired => { + consume_bool(data)?; + KeyParam::TrustedUserPresenceRequired + } + Tag::TrustedConfirmationRequired => { + consume_bool(data)?; + KeyParam::TrustedConfirmationRequired + } + Tag::UnlockedDeviceRequired => { + consume_bool(data)?; + KeyParam::UnlockedDeviceRequired + } + Tag::DeviceUniqueAttestation => { + consume_bool(data)?; + KeyParam::DeviceUniqueAttestation + } + Tag::StorageKey => { + consume_bool(data)?; + KeyParam::StorageKey + } + Tag::ResetSinceIdRotation => { + consume_bool(data)?; + KeyParam::ResetSinceIdRotation + } + + // `DateTime`-holding variants. + Tag::ActiveDatetime => { + KeyParam::ActiveDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) + } + Tag::OriginationExpireDatetime => { + KeyParam::OriginationExpireDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) + } + Tag::UsageExpireDatetime => { + KeyParam::UsageExpireDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) + } + Tag::CreationDatetime => { + KeyParam::CreationDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) + } + Tag::CertificateNotBefore => { + KeyParam::CertificateNotBefore(DateTime { ms_since_epoch: consume_i64(data)? }) + } + Tag::CertificateNotAfter => { + KeyParam::CertificateNotAfter(DateTime { ms_since_epoch: consume_i64(data)? }) + } + + // `Vec`-holding variants. + Tag::ApplicationId => { + KeyParam::ApplicationId(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::ApplicationData => { + KeyParam::ApplicationData(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::AttestationChallenge => KeyParam::AttestationChallenge(consume_blob( + data, + &mut next_blob_offset, + blob_data, + )?), + Tag::AttestationApplicationId => KeyParam::AttestationApplicationId(consume_blob( + data, + &mut next_blob_offset, + blob_data, + )?), + Tag::AttestationIdBrand => { + KeyParam::AttestationIdBrand(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::AttestationIdDevice => { + KeyParam::AttestationIdDevice(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::AttestationIdProduct => KeyParam::AttestationIdProduct(consume_blob( + data, + &mut next_blob_offset, + blob_data, + )?), + Tag::AttestationIdSerial => { + KeyParam::AttestationIdSerial(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::AttestationIdImei => { + KeyParam::AttestationIdImei(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::AttestationIdSecondImei => KeyParam::AttestationIdSecondImei(consume_blob( + data, + &mut next_blob_offset, + blob_data, + )?), + Tag::AttestationIdMeid => { + KeyParam::AttestationIdMeid(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::AttestationIdManufacturer => KeyParam::AttestationIdManufacturer(consume_blob( + data, + &mut next_blob_offset, + blob_data, + )?), + Tag::AttestationIdModel => { + KeyParam::AttestationIdModel(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::Nonce => KeyParam::Nonce(consume_blob(data, &mut next_blob_offset, blob_data)?), + Tag::RootOfTrust => { + KeyParam::RootOfTrust(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::CertificateSerial => { + KeyParam::CertificateSerial(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::CertificateSubject => { + KeyParam::CertificateSubject(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + Tag::ModuleHash => { + KeyParam::ModuleHash(consume_blob(data, &mut next_blob_offset, blob_data)?) + } + // Invalid variants. + Tag::Invalid + | Tag::HardwareType + | Tag::MinSecondsBetweenOps + | Tag::UniqueId + | Tag::IdentityCredentialKey + | Tag::AssociatedData + | Tag::ConfirmationToken => { + return Err(km_err!(InvalidKeyBlob, "invalid tag {:?} encountered", tag)); + } + })?; + } + + Ok(results) +} + +/// Determine ordering of two [`KeyParam`] values for legacy key blob ordering. +/// Invalid parameters are likely to compare equal. +pub fn param_compare(left: &KeyParam, right: &KeyParam) -> Ordering { + match (left, right) { + (KeyParam::Purpose(l), KeyParam::Purpose(r)) => l.cmp(r), + (KeyParam::Algorithm(l), KeyParam::Algorithm(r)) => l.cmp(r), + (KeyParam::KeySize(l), KeyParam::KeySize(r)) => l.cmp(r), + (KeyParam::BlockMode(l), KeyParam::BlockMode(r)) => l.cmp(r), + (KeyParam::Digest(l), KeyParam::Digest(r)) => l.cmp(r), + (KeyParam::Padding(l), KeyParam::Padding(r)) => l.cmp(r), + (KeyParam::CallerNonce, KeyParam::CallerNonce) => Ordering::Equal, + (KeyParam::MinMacLength(l), KeyParam::MinMacLength(r)) => l.cmp(r), + (KeyParam::EcCurve(l), KeyParam::EcCurve(r)) => l.cmp(r), + (KeyParam::RsaPublicExponent(l), KeyParam::RsaPublicExponent(r)) => l.cmp(r), + (KeyParam::IncludeUniqueId, KeyParam::IncludeUniqueId) => Ordering::Equal, + (KeyParam::RsaOaepMgfDigest(l), KeyParam::RsaOaepMgfDigest(r)) => l.cmp(r), + (KeyParam::BootloaderOnly, KeyParam::BootloaderOnly) => Ordering::Equal, + (KeyParam::RollbackResistance, KeyParam::RollbackResistance) => Ordering::Equal, + (KeyParam::EarlyBootOnly, KeyParam::EarlyBootOnly) => Ordering::Equal, + (KeyParam::ActiveDatetime(l), KeyParam::ActiveDatetime(r)) => l.cmp(r), + (KeyParam::OriginationExpireDatetime(l), KeyParam::OriginationExpireDatetime(r)) => { + l.cmp(r) + } + (KeyParam::UsageExpireDatetime(l), KeyParam::UsageExpireDatetime(r)) => l.cmp(r), + (KeyParam::MaxUsesPerBoot(l), KeyParam::MaxUsesPerBoot(r)) => l.cmp(r), + (KeyParam::UsageCountLimit(l), KeyParam::UsageCountLimit(r)) => l.cmp(r), + (KeyParam::UserId(l), KeyParam::UserId(r)) => l.cmp(r), + (KeyParam::UserSecureId(l), KeyParam::UserSecureId(r)) => l.cmp(r), + (KeyParam::NoAuthRequired, KeyParam::NoAuthRequired) => Ordering::Equal, + (KeyParam::UserAuthType(l), KeyParam::UserAuthType(r)) => l.cmp(r), + (KeyParam::AuthTimeout(l), KeyParam::AuthTimeout(r)) => l.cmp(r), + (KeyParam::AllowWhileOnBody, KeyParam::AllowWhileOnBody) => Ordering::Equal, + (KeyParam::TrustedUserPresenceRequired, KeyParam::TrustedUserPresenceRequired) => { + Ordering::Equal + } + (KeyParam::TrustedConfirmationRequired, KeyParam::TrustedConfirmationRequired) => { + Ordering::Equal + } + (KeyParam::UnlockedDeviceRequired, KeyParam::UnlockedDeviceRequired) => Ordering::Equal, + (KeyParam::ApplicationId(l), KeyParam::ApplicationId(r)) => l.cmp(r), + (KeyParam::ApplicationData(l), KeyParam::ApplicationData(r)) => l.cmp(r), + (KeyParam::CreationDatetime(l), KeyParam::CreationDatetime(r)) => l.cmp(r), + (KeyParam::Origin(l), KeyParam::Origin(r)) => l.cmp(r), + (KeyParam::RootOfTrust(l), KeyParam::RootOfTrust(r)) => l.cmp(r), + (KeyParam::OsVersion(l), KeyParam::OsVersion(r)) => l.cmp(r), + (KeyParam::OsPatchlevel(l), KeyParam::OsPatchlevel(r)) => l.cmp(r), + (KeyParam::AttestationChallenge(l), KeyParam::AttestationChallenge(r)) => l.cmp(r), + (KeyParam::AttestationApplicationId(l), KeyParam::AttestationApplicationId(r)) => l.cmp(r), + (KeyParam::AttestationIdBrand(l), KeyParam::AttestationIdBrand(r)) => l.cmp(r), + (KeyParam::AttestationIdDevice(l), KeyParam::AttestationIdDevice(r)) => l.cmp(r), + (KeyParam::AttestationIdProduct(l), KeyParam::AttestationIdProduct(r)) => l.cmp(r), + (KeyParam::AttestationIdSerial(l), KeyParam::AttestationIdSerial(r)) => l.cmp(r), + (KeyParam::AttestationIdImei(l), KeyParam::AttestationIdImei(r)) => l.cmp(r), + (KeyParam::AttestationIdSecondImei(l), KeyParam::AttestationIdSecondImei(r)) => l.cmp(r), + (KeyParam::AttestationIdMeid(l), KeyParam::AttestationIdMeid(r)) => l.cmp(r), + (KeyParam::AttestationIdManufacturer(l), KeyParam::AttestationIdManufacturer(r)) => { + l.cmp(r) + } + (KeyParam::AttestationIdModel(l), KeyParam::AttestationIdModel(r)) => l.cmp(r), + (KeyParam::VendorPatchlevel(l), KeyParam::VendorPatchlevel(r)) => l.cmp(r), + (KeyParam::BootPatchlevel(l), KeyParam::BootPatchlevel(r)) => l.cmp(r), + (KeyParam::DeviceUniqueAttestation, KeyParam::DeviceUniqueAttestation) => Ordering::Equal, + (KeyParam::StorageKey, KeyParam::StorageKey) => Ordering::Equal, + (KeyParam::Nonce(l), KeyParam::Nonce(r)) => l.cmp(r), + (KeyParam::MacLength(l), KeyParam::MacLength(r)) => l.cmp(r), + (KeyParam::ResetSinceIdRotation, KeyParam::ResetSinceIdRotation) => Ordering::Equal, + (KeyParam::CertificateSerial(l), KeyParam::CertificateSerial(r)) => l.cmp(r), + (KeyParam::CertificateSubject(l), KeyParam::CertificateSubject(r)) => l.cmp(r), + (KeyParam::CertificateNotBefore(l), KeyParam::CertificateNotBefore(r)) => l.cmp(r), + (KeyParam::CertificateNotAfter(l), KeyParam::CertificateNotAfter(r)) => l.cmp(r), + (KeyParam::MaxBootLevel(l), KeyParam::MaxBootLevel(r)) => l.cmp(r), + (KeyParam::ModuleHash(l), KeyParam::ModuleHash(r)) => l.cmp(r), + + (left, right) => left.tag().cmp(&right.tag()), + } +} diff --git a/libs/rust/common/src/tag/tests.rs b/libs/rust/common/src/tag/tests.rs new file mode 100644 index 0000000..9b215c7 --- /dev/null +++ b/libs/rust/common/src/tag/tests.rs @@ -0,0 +1,138 @@ +// Copyright 2022, 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. + +use super::*; +use crate::expect_err; +use kmr_wire::{keymint::KeyParam, KeySizeInBits}; + +#[test] +fn test_characteristics_invalid() { + let tests = vec![ + (vec![KeyParam::UsageCountLimit(42), KeyParam::UsageCountLimit(43)], "duplicate value"), + (vec![KeyParam::Nonce(vec![1, 2])], "not a valid key characteristic"), + ]; + for (characteristics, msg) in tests { + let result = crate::tag::characteristics_valid(&characteristics); + expect_err!(result, msg); + } +} + +#[test] +fn test_legacy_serialization() { + let tests = vec![( + concat!( + "00000000", // blob data size + "03000000", // param count + "15000000", // param size + "02000010", // Tag::ALGORITHM = 268435458 = 0x10000002, + "20000000", // Algorithm::AES + "03000030", // Tag::KEY_SIZE = 805306371 = 0x30000003 + "00010000", // size = 0x00000100 + "fb010070", // Tag::TRUSTED_USER_PRESENCE_REQUIRED = 0x700001fb + "01", // True + ), + vec![ + KeyParam::Algorithm(Algorithm::Aes), + KeyParam::KeySize(KeySizeInBits(256)), + KeyParam::TrustedUserPresenceRequired, + ], + )]; + + for (hex_data, want_params) in tests { + let want_data = hex::decode(hex_data).unwrap(); + + let got_data = legacy::serialize(&want_params).unwrap(); + assert_eq!(hex::encode(got_data), hex_data); + + let mut data = &want_data[..]; + let got_params = legacy::deserialize(&mut data).unwrap(); + assert!(data.is_empty(), "data left: {}", hex::encode(data)); + assert_eq!(got_params, want_params); + } +} + +#[test] +fn test_check_begin_params_fail() { + let chars = vec![ + KeyParam::NoAuthRequired, + KeyParam::Algorithm(Algorithm::Hmac), + KeyParam::KeySize(KeySizeInBits(128)), + KeyParam::Digest(Digest::Sha256), + KeyParam::Purpose(KeyPurpose::Sign), + KeyParam::Purpose(KeyPurpose::Verify), + KeyParam::MinMacLength(160), + ]; + + let tests = vec![ + ( + KeyPurpose::Encrypt, + vec![KeyParam::Digest(Digest::Sha256), KeyParam::MacLength(160)], + "invalid purpose Encrypt", + ), + (KeyPurpose::Sign, vec![KeyParam::Digest(Digest::Sha256)], "MissingMacLength"), + ( + KeyPurpose::Sign, + vec![KeyParam::Digest(Digest::Sha512), KeyParam::MacLength(160)], + "not in key characteristics", + ), + ]; + for (purpose, params, msg) in tests { + expect_err!(check_begin_params(&chars, purpose, ¶ms), msg); + } +} + +#[test] +fn test_copyable_tags() { + for tag in UNPOLICED_COPYABLE_TAGS { + let info = info(*tag).unwrap(); + assert!(info.user_can_specify.0, "tag {:?} not listed as user-specifiable", tag); + assert!( + info.characteristic == info::Characteristic::KeyMintEnforced + || info.characteristic == info::Characteristic::KeystoreEnforced + || info.characteristic == info::Characteristic::BothEnforced, + "tag {:?} with unexpected characteristic {:?}", + tag, + info.characteristic + ); + } +} + +#[test] +fn test_luhn_checksum() { + let tests = vec![(0, 0), (7992739871, 3), (735423462345, 6), (721367498765427, 4)]; + for (input, want) in tests { + let got = luhn_checksum(input); + assert_eq!(got, want, "mismatch for input {}", input); + } +} + +#[test] +fn test_increment_imei() { + let tests = vec![ + // Anything that's not ASCII digits gives empty vec. + ("", ""), + ("01", ""), + ("01", ""), + ("7576", ""), + ("c328", ""), // Invalid UTF-8 + // 721367498765404 => 721367498765412 + ("373231333637343938373635343034", "373231333637343938373635343132"), + ("39393930", "3130303039"), // String gets longer + ]; + for (input, want) in tests { + let input_data = hex::decode(input).unwrap(); + let got = increment_imei(&input_data); + assert_eq!(hex::encode(got), want, "mismatch for input IMEI {}", input); + } +} diff --git a/libs/rust/derive/Cargo.toml b/libs/rust/derive/Cargo.toml new file mode 100644 index 0000000..9a21207 --- /dev/null +++ b/libs/rust/derive/Cargo.toml @@ -0,0 +1,22 @@ +# Note that Cargo is not an officially supported build tool (Android's Soong is the official +# tool). This Cargo.toml file is included purely for the convenience of KeyMint developers. + +[package] +name = "kmr-derive" +version = "0.1.0" +authors = ["David Drysdale "] +edition = "2021" +license = "Apache-2.0" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "^1.0" +quote = "^1.0" +syn = { version = "2.0.38", features = ["derive", "parsing"] } + +[dev-dependencies] +ciborium = { version = "^0.2.0", default-features = false } +ciborium-io = "^0.2.0" +kmr-wire = "*" diff --git a/libs/rust/derive/src/lib.rs b/libs/rust/derive/src/lib.rs new file mode 100644 index 0000000..37439e3 --- /dev/null +++ b/libs/rust/derive/src/lib.rs @@ -0,0 +1,597 @@ +// Copyright 2022, 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. + +//! Derive macro for `AsCborValue`. +use proc_macro2::TokenStream; +use quote::{format_ident, quote, quote_spanned}; +use syn::{ + parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, Fields, GenericParam, + Generics, Ident, Index, +}; + +/// Derive macro that implements the `AsCborValue` trait. Using this macro requires +/// that `AsCborValue`, `CborError` and `cbor_type_error` are locally `use`d. +#[proc_macro_derive(AsCborValue)] +pub fn derive_as_cbor_value(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + derive_as_cbor_value_internal(&input) +} + +fn derive_as_cbor_value_internal(input: &DeriveInput) -> proc_macro::TokenStream { + let name = &input.ident; + + // Add a bound `T: AsCborValue` for every type parameter `T`. + let generics = add_trait_bounds(&input.generics); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let from_val = from_val_struct(&input.data); + let to_val = to_val_struct(&input.data); + let cddl = cddl_struct(name, &input.data); + + let expanded = quote! { + // The generated impl + impl #impl_generics AsCborValue for #name #ty_generics #where_clause { + fn from_cbor_value(value: ciborium::value::Value) -> Result { + #from_val + } + fn to_cbor_value(self) -> Result { + #to_val + } + fn cddl_typename() -> Option { + Some(stringify!(#name).to_string()) + } + fn cddl_schema() -> Option { + #cddl + } + } + }; + + expanded.into() +} + +/// Add a bound `T: AsCborValue` for every type parameter `T`. +fn add_trait_bounds(generics: &Generics) -> Generics { + let mut generics = generics.clone(); + for param in &mut generics.params { + if let GenericParam::Type(ref mut type_param) = *param { + type_param.bounds.push(parse_quote!(AsCborValue)); + } + } + generics +} + +/// Generate an expression to convert an instance of a compound type to `ciborium::value::Value` +fn to_val_struct(data: &Data) -> TokenStream { + match *data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // { + // let mut v = Vec::new(); + // v.try_reserve(3).map_err(|_e| CborError::AllocationFailed)?; + // v.push(AsCborValue::to_cbor_value(self.x)?); + // v.push(AsCborValue::to_cbor_value(self.y)?); + // v.push(AsCborValue::to_cbor_value(self.z)?); + // Ok(ciborium::value::Value::Array(v)) + // } + let nfields = fields.named.len(); + let recurse = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! {f.span()=> + v.push(AsCborValue::to_cbor_value(self.#name)?) + } + }); + quote! { + { + let mut v = Vec::new(); + v.try_reserve(#nfields).map_err(|_e| CborError::AllocationFailed)?; + #(#recurse; )* + Ok(ciborium::value::Value::Array(v)) + } + } + } + Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { + // For a newtype, expands to an expression + // + // self.0.to_cbor_value() + quote! { + self.0.to_cbor_value() + } + } + Fields::Unnamed(ref fields) => { + // Expands to an expression like + // + // + // { + // let mut v = Vec::new(); + // v.try_reserve(3).map_err(|_e| CborError::AllocationFailed)?; + // v.push(AsCborValue::to_cbor_value(self.0)?); + // v.push(AsCborValue::to_cbor_value(self.1)?); + // v.push(AsCborValue::to_cbor_value(self.2)?); + // Ok(ciborium::value::Value::Array(v)) + // } + let nfields = fields.unnamed.len(); + let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| { + let index = Index::from(i); + quote_spanned! {f.span()=> + v.push(AsCborValue::to_cbor_value(self.#index)?) + } + }); + quote! { + { + let mut v = Vec::new(); + v.try_reserve(#nfields).map_err(|_e| CborError::AllocationFailed)?; + #(#recurse; )* + Ok(ciborium::value::Value::Array(v)) + } + } + } + Fields::Unit => unimplemented!(), + } + } + Data::Enum(_) => { + quote! { + let v: ciborium::value::Integer = (self as i32).into(); + Ok(ciborium::value::Value::Integer(v)) + } + } + Data::Union(_) => unimplemented!(), + } +} + +/// Generate an expression to convert a `ciborium::value::Value` into an instance of a compound +/// type. +fn from_val_struct(data: &Data) -> TokenStream { + match data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // let mut a = match value { + // ciborium::value::Value::Array(a) => a, + // _ => return cbor_type_error(&value, "arr"), + // }; + // if a.len() != 3 { + // return Err(CborError::UnexpectedItem("arr", "arr len 3")); + // } + // // Fields specified in reverse order to reduce shifting. + // Ok(Self { + // z: ::from_cbor_value(a.remove(2))?, + // y: ::from_cbor_value(a.remove(1))?, + // x: ::from_cbor_value(a.remove(0))?, + // }) + // + // but using fully qualified function call syntax. + let nfields = fields.named.len(); + let recurse = fields.named.iter().enumerate().rev().map(|(i, f)| { + let name = &f.ident; + let index = Index::from(i); + let typ = &f.ty; + quote_spanned! {f.span()=> + #name: <#typ>::from_cbor_value(a.remove(#index))? + } + }); + quote! { + let mut a = match value { + ciborium::value::Value::Array(a) => a, + _ => return cbor_type_error(&value, "arr"), + }; + if a.len() != #nfields { + return Err(CborError::UnexpectedItem( + "arr", + concat!("arr len ", stringify!(#nfields)), + )); + } + // Fields specified in reverse order to reduce shifting. + Ok(Self { + #(#recurse, )* + }) + } + } + Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { + // For a newtype, expands to an expression like + // + // Ok(Self(::from_cbor_value(value)?)) + let inner = fields.unnamed.first().unwrap(); + let typ = &inner.ty; + quote! { + Ok(Self(<#typ>::from_cbor_value(value)?)) + } + } + Fields::Unnamed(ref fields) => { + // Expands to an expression like + // + // let mut a = match value { + // ciborium::value::Value::Array(a) => a, + // _ => return cbor_type_error(&value, "arr"), + // }; + // if a.len() != 3 { + // return Err(CborError::UnexpectedItem("arr", "arr len 3")); + // } + // // Fields specified in reverse order to reduce shifting. + // let field_2 = ::from_cbor_value(a.remove(2))?; + // let field_1 = ::from_cbor_value(a.remove(1))?; + // let field_0 = ::from_cbor_value(a.remove(0))?; + // Ok(Self(field_0, field_1, field_2)) + let nfields = fields.unnamed.len(); + let recurse1 = fields.unnamed.iter().enumerate().rev().map(|(i, f)| { + let typ = &f.ty; + let varname = format_ident!("field_{}", i); + quote_spanned! {f.span()=> + let #varname = <#typ>::from_cbor_value(a.remove(#i))?; + } + }); + let recurse2 = fields.unnamed.iter().enumerate().map(|(i, f)| { + let varname = format_ident!("field_{}", i); + quote_spanned! {f.span()=> + #varname + } + }); + quote! { + let mut a = match value { + ciborium::value::Value::Array(a) => a, + _ => return cbor_type_error(&value, "arr"), + }; + if a.len() != #nfields { + return Err(CborError::UnexpectedItem("arr", + concat!("arr len ", + stringify!(#nfields)))); + } + // Fields specified in reverse order to reduce shifting. + #(#recurse1)* + + Ok(Self( #(#recurse2, )* )) + } + } + Fields::Unit => unimplemented!(), + } + } + Data::Enum(enum_data) => { + // This only copes with variants with no fields. + // Expands to an expression like: + // + // use core::convert::TryInto; + // let v: i32 = match value { + // ciborium::value::Value::Integer(i) => i.try_into().map_err(|_| { + // CborError::OutOfRangeIntegerValue + // })?, + // v => return cbor_type_error(&v, &"int"), + // }; + // match v { + // x if x == Self::Variant1 as i32 => Ok(Self::Variant1), + // x if x == Self::Variant2 as i32 => Ok(Self::Variant2), + // x if x == Self::Variant3 as i32 => Ok(Self::Variant3), + // _ => Err( CborError::OutOfRangeIntegerValue), + // } + let recurse = enum_data.variants.iter().map(|variant| { + let vname = &variant.ident; + quote_spanned! {variant.span()=> + x if x == Self::#vname as i32 => Ok(Self::#vname), + } + }); + + quote! { + use core::convert::TryInto; + // First get the int value as an `i32`. + let v: i32 = match value { + ciborium::value::Value::Integer(i) => i.try_into().map_err(|_| { + CborError::OutOfRangeIntegerValue + })?, + v => return cbor_type_error(&v, &"int"), + }; + // Now match against enum possibilities. + match v { + #(#recurse)* + _ => Err( + CborError::OutOfRangeIntegerValue + ), + } + } + } + Data::Union(_) => unimplemented!(), + } +} + +/// Generate an expression that expresses the CDDL schema for the type. +fn cddl_struct(name: &Ident, data: &Data) -> TokenStream { + match *data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + if fields.named.iter().next().is_none() { + return quote! { + Some(format!("[]")) + }; + } + // Expands to an expression like + // + // format!("[ + // x: {}, + // y: {}, + // z: {}, + // ]", + // ::cddl_ref(), + // ::cddl_ref(), + // ::cddl_ref(), + // ) + let fmt_recurse = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! {f.span()=> + concat!(" ", stringify!(#name), ": {},\n") + } + }); + let fmt = quote! { + concat!("[\n", + #(#fmt_recurse, )* + "]") + }; + let recurse = fields.named.iter().map(|f| { + let typ = &f.ty; + quote_spanned! {f.span()=> + <#typ>::cddl_ref() + } + }); + quote! { + Some(format!( + #fmt, + #(#recurse, )* + )) + } + } + Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let inner = fields.unnamed.first().unwrap(); + let typ = &inner.ty; + quote! { + Some(<#typ>::cddl_ref()) + } + } + Fields::Unnamed(ref fields) => { + if fields.unnamed.iter().next().is_none() { + return quote! { + Some(format!("()")) + }; + } + // Expands to an expression like + // + // format!("[ + // {}, + // {}, + // {}, + // ]", + // ::cddl_ref(), + // ::cddl_ref(), + // ::cddl_ref(), + // ) + // + let fmt_recurse = fields.unnamed.iter().map(|f| { + quote_spanned! {f.span()=> + " {},\n" + } + }); + let fmt = quote! { + concat!("[\n", + #(#fmt_recurse, )* + "]") + }; + let recurse = fields.unnamed.iter().map(|f| { + let typ = &f.ty; + quote_spanned! {f.span()=> + <#typ>::cddl_ref() + } + }); + quote! { + Some(format!( + #fmt, + #(#recurse, )* + )) + } + } + Fields::Unit => unimplemented!(), + } + } + Data::Enum(ref enum_data) => { + // This only copes with variants with no fields. + // Expands to an expression like: + // + // format!("&( + // EnumName_Variant1: {}, + // EnumName_Variant2: {}, + // EnumName_Variant3: {}, + // )", + // Self::Variant1 as i32, + // Self::Variant2 as i32, + // Self::Variant3 as i32, + // ) + // + let fmt_recurse = enum_data.variants.iter().map(|variant| { + let vname = &variant.ident; + quote_spanned! {variant.span()=> + concat!(" ", + stringify!(#name), + "_", + stringify!(#vname), + ": {},\n") + } + }); + let fmt = quote! { + concat!("&(\n", + #(#fmt_recurse, )* + ")") + }; + let recurse = enum_data.variants.iter().map(|variant| { + let vname = &variant.ident; + quote_spanned! {variant.span()=> + Self::#vname as i32 + } + }); + quote! { + Some(format!( + #fmt, + #(#recurse, )* + )) + } + } + Data::Union(_) => unimplemented!(), + } +} + +/// Derive macro that implements a `from_raw_tag_value` method for the `Tag` enum. +#[proc_macro_derive(FromRawTag)] +pub fn derive_from_raw_tag(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + derive_from_raw_tag_internal(&input) +} + +fn derive_from_raw_tag_internal(input: &DeriveInput) -> proc_macro::TokenStream { + let name = &input.ident; + let from_val = from_raw_tag(name, &input.data); + let expanded = quote! { + pub fn from_raw_tag_value(raw_tag: u32) -> #name { + #from_val + } + }; + expanded.into() +} + +/// Generate an expression to convert a `u32` into an instance of an fieldless enum. +/// Assumes the existence of an `Invalid` variant as a fallback, and assumes that a +/// `raw_tag_value` function is in scope. +fn from_raw_tag(name: &Ident, data: &Data) -> TokenStream { + match data { + Data::Enum(enum_data) => { + let recurse = enum_data.variants.iter().map(|variant| { + let vname = &variant.ident; + quote_spanned! {variant.span()=> + x if x == raw_tag_value(#name::#vname) => #name::#vname, + } + }); + + quote! { + match raw_tag { + #(#recurse)* + _ => #name::Invalid, + } + } + } + _ => unimplemented!(), + } +} + +/// Derive macro that implements the `legacy::InnerSerialize` trait. Using this macro requires +/// that `InnerSerialize` and `Error` from `kmr_wire::legacy` be locally `use`d. +#[proc_macro_derive(LegacySerialize)] +pub fn derive_legacy_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + derive_legacy_serialize_internal(&input) +} + +fn derive_legacy_serialize_internal(input: &DeriveInput) -> proc_macro::TokenStream { + let name = &input.ident; + + let deserialize_val = deserialize_struct(&input.data); + let serialize_val = serialize_struct(&input.data); + + let expanded = quote! { + impl InnerSerialize for #name { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + #deserialize_val + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + #serialize_val + } + } + }; + + expanded.into() +} + +fn deserialize_struct(data: &Data) -> TokenStream { + match data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // let (x, data) = ::deserialize(data)?; + // let (y, data) = ::deserialize(data)?; + // let (z, data) = ::deserialize(data)?; + // Ok((Self { + // x, + // y, + // z, + // }, data)) + // + let recurse1 = fields.named.iter().map(|f| { + let name = &f.ident; + let typ = &f.ty; + quote_spanned! {f.span()=> + let (#name, data) = <#typ>::deserialize(data)?; + } + }); + let recurse2 = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! {f.span()=> + #name + } + }); + quote! { + #(#recurse1)* + Ok((Self { + #(#recurse2, )* + }, data)) + } + } + Fields::Unnamed(_) => unimplemented!(), + Fields::Unit => unimplemented!(), + } + } + Data::Enum(_) => unimplemented!(), + Data::Union(_) => unimplemented!(), + } +} + +fn serialize_struct(data: &Data) -> TokenStream { + match data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // self.x.serialize_into(buf)?; + // self.y.serialize_into(buf)?; + // self.z.serialize_into(buf)?; + // Ok(()) + // + let recurse = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! {f.span()=> + self.#name.serialize_into(buf)?; + } + }); + quote! { + #(#recurse)* + Ok(()) + } + } + Fields::Unnamed(_) => unimplemented!(), + Fields::Unit => unimplemented!(), + } + } + Data::Enum(_) => unimplemented!(), + Data::Union(_) => unimplemented!(), + } +} diff --git a/libs/rust/derive/tests/integration_test.rs b/libs/rust/derive/tests/integration_test.rs new file mode 100644 index 0000000..5ba34d9 --- /dev/null +++ b/libs/rust/derive/tests/integration_test.rs @@ -0,0 +1,65 @@ +// Copyright 2022, 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. + +use kmr_derive::AsCborValue; +use kmr_wire::{cbor_type_error, AsCborValue, CborError}; + +#[derive(Clone, Debug, PartialEq, Eq, AsCborValue)] +struct NamedFields { + i: i32, + s: String, +} + +#[test] +fn test_derive_named_struct_roundtrip() { + let want = NamedFields { i: 42, s: "a string".to_string() }; + let want_value = want.clone().to_cbor_value().unwrap(); + let got = NamedFields::from_cbor_value(want_value).unwrap(); + assert_eq!(want, got); + assert_eq!(NamedFields::cddl_typename().unwrap(), "NamedFields"); + assert_eq!(NamedFields::cddl_schema().unwrap(), "[\n i: int,\n s: tstr,\n]"); +} + +#[derive(Clone, Debug, PartialEq, Eq, AsCborValue)] +struct UnnamedFields(i32, String); + +#[test] +fn test_derive_unnamed_struct_roundtrip() { + let want = UnnamedFields(42, "a string".to_string()); + let want_value = want.clone().to_cbor_value().unwrap(); + let got = UnnamedFields::from_cbor_value(want_value).unwrap(); + assert_eq!(want, got); + assert_eq!(UnnamedFields::cddl_typename().unwrap(), "UnnamedFields"); + assert_eq!(UnnamedFields::cddl_schema().unwrap(), "[\n int,\n tstr,\n]"); +} + +#[derive(Clone, Debug, PartialEq, Eq, AsCborValue)] +enum NumericEnum { + One = 1, + Two = 2, + Three = 3, +} + +#[test] +fn test_derive_numeric_enum_roundtrip() { + let want = NumericEnum::Two; + let want_value = want.clone().to_cbor_value().unwrap(); + let got = NumericEnum::from_cbor_value(want_value).unwrap(); + assert_eq!(want, got); + assert_eq!(NumericEnum::cddl_typename().unwrap(), "NumericEnum"); + assert_eq!( + NumericEnum::cddl_schema().unwrap(), + "&(\n NumericEnum_One: 1,\n NumericEnum_Two: 2,\n NumericEnum_Three: 3,\n)" + ); +} diff --git a/libs/rust/hal/src/env.rs b/libs/rust/hal/src/env.rs new file mode 100644 index 0000000..6f4acf7 --- /dev/null +++ b/libs/rust/hal/src/env.rs @@ -0,0 +1,204 @@ +// Copyright 2022, 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. + +//! Retrieve and populate information about userspace. + +use kmr_wire::SetHalInfoRequest; +use regex::Regex; + +// The OS version property is of form "12" or "12.1" or "12.1.3". +const OS_VERSION_PROPERTY: &str = "ro.build.version.release"; +const OS_VERSION_REGEX: &str = r"^(?P\d{1,2})(\.(?P\d{1,2}))?(\.(?P\d{1,2}))?$"; + +// The patchlevel properties are of form "YYYY-MM-DD". +/// Name of property that holds the OS patchlevel. +pub const OS_PATCHLEVEL_PROPERTY: &str = "ro.build.version.security_patch"; +const VENDOR_PATCHLEVEL_PROPERTY: &str = "ro.vendor.build.security_patch"; +const PATCHLEVEL_REGEX: &str = r"^(?P\d{4})-(?P\d{2})-(?P\d{2})$"; + +// Just use [`String`] for errors here. +type Error = String; + +/// Retrieve a numeric value from a possible match. +fn extract_u32(value: Option) -> Result { + match value { + Some(m) => { + let s = m.as_str(); + match s.parse::() { + Ok(v) => Ok(v), + Err(e) => Err(format!("failed to parse integer: {:?}", e)), + } + } + None => Err("failed to find match".to_string()), + } +} + +/// Retrieve the value of a property identified by `name`. +pub fn get_property(name: &str) -> Result { + match rustutils::system_properties::read(name) { + Ok(Some(value)) => Ok(value), + Ok(None) => Err(format!("no value for property {}", name)), + Err(e) => Err(format!("failed to get property {}: {:?}", name, e)), + } +} + +/// Extract a patchlevel in form YYYYMM from a "YYYY-MM-DD" property value. +pub fn extract_truncated_patchlevel(prop_value: &str) -> Result { + let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX) + .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?; + + let captures = patchlevel_regex + .captures(prop_value) + .ok_or_else(|| "failed to match patchlevel regex".to_string())?; + let year = extract_u32(captures.name("year"))?; + let month = extract_u32(captures.name("month"))?; + if !(1..=12).contains(&month) { + return Err(format!("month out of range: {}", month)); + } + // no day + Ok(year * 100 + month) +} + +/// Extract a patchlevel in form YYYYMMDD from a "YYYY-MM-DD" property value. +pub fn extract_patchlevel(prop_value: &str) -> Result { + let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX) + .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?; + + let captures = patchlevel_regex + .captures(prop_value) + .ok_or_else(|| "failed to match patchlevel regex".to_string())?; + let year = extract_u32(captures.name("year"))?; + let month = extract_u32(captures.name("month"))?; + if !(1..=12).contains(&month) { + return Err(format!("month out of range: {}", month)); + } + let day = extract_u32(captures.name("day"))?; + if !(1..=31).contains(&day) { + return Err(format!("day out of range: {}", day)); + } + Ok(year * 10000 + month * 100 + day) +} + +/// Generate HAL information from property values. +fn populate_hal_info_from( + os_version_prop: &str, + os_patchlevel_prop: &str, + vendor_patchlevel_prop: &str, +) -> Result { + let os_version_regex = Regex::new(OS_VERSION_REGEX) + .map_err(|e| format!("failed to compile version regexp: {:?}", e))?; + let captures = os_version_regex + .captures(os_version_prop) + .ok_or_else(|| "failed to match OS version regex".to_string())?; + let major = extract_u32(captures.name("major"))?; + let minor = extract_u32(captures.name("minor")).unwrap_or(0u32); + let sub = extract_u32(captures.name("sub")).unwrap_or(0u32); + let os_version = (major * 10000) + (minor * 100) + sub; + + Ok(SetHalInfoRequest { + os_version, + os_patchlevel: extract_truncated_patchlevel(os_patchlevel_prop)?, + vendor_patchlevel: extract_patchlevel(vendor_patchlevel_prop)?, + }) +} + +/// Populate a [`SetHalInfoRequest`] based on property values read from the environment. +pub fn populate_hal_info() -> Result { + let os_version_prop = get_property(OS_VERSION_PROPERTY) + .map_err(|e| format!("failed to retrieve property: {:?}", e))?; + let os_patchlevel_prop = get_property(OS_PATCHLEVEL_PROPERTY) + .map_err(|e| format!("failed to retrieve property: {:?}", e))?; + let vendor_patchlevel_prop = get_property(VENDOR_PATCHLEVEL_PROPERTY) + .map_err(|e| format!("failed to retrieve property: {:?}", e))?; + + populate_hal_info_from(&os_version_prop, &os_patchlevel_prop, &vendor_patchlevel_prop) +} + +#[cfg(test)] +mod tests { + use super::*; + use kmr_wire::SetHalInfoRequest; + #[test] + fn test_hal_info() { + let tests = vec![ + ( + "12", + "2021-02-02", + "2022-03-04", + SetHalInfoRequest { + os_version: 120000, + os_patchlevel: 202102, + vendor_patchlevel: 20220304, + }, + ), + ( + "12.5", + "2021-02-02", + "2022-03-04", + SetHalInfoRequest { + os_version: 120500, + os_patchlevel: 202102, + vendor_patchlevel: 20220304, + }, + ), + ( + "12.5.7", + "2021-02-02", + "2022-03-04", + SetHalInfoRequest { + os_version: 120507, + os_patchlevel: 202102, + vendor_patchlevel: 20220304, + }, + ), + ]; + for (os_version, os_patch, vendor_patch, want) in tests { + let got = populate_hal_info_from(os_version, os_patch, vendor_patch).unwrap(); + assert_eq!( + got, want, + "Mismatch for input ({}, {}, {})", + os_version, os_patch, vendor_patch + ); + } + } + + #[test] + fn test_invalid_hal_info() { + let tests = vec![ + ("xx", "2021-02-02", "2022-03-04", "failed to match OS version"), + ("12.xx", "2021-02-02", "2022-03-04", "failed to match OS version"), + ("12.5.xx", "2021-02-02", "2022-03-04", "failed to match OS version"), + ("12", "20212-02-02", "2022-03-04", "failed to match patchlevel regex"), + ("12", "2021-xx-02", "2022-03-04", "failed to match patchlevel"), + ("12", "2021-13-02", "2022-03-04", "month out of range"), + ("12", "2022-03-04", "2021-xx-02", "failed to match patchlevel"), + ("12", "2022-03-04", "2021-13-02", "month out of range"), + ("12", "2022-03-04", "2021-03-32", "day out of range"), + ]; + for (os_version, os_patch, vendor_patch, want_err) in tests { + let result = populate_hal_info_from(os_version, os_patch, vendor_patch); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.contains(want_err), + "Mismatch for input ({}, {}, {}), got error '{}', want '{}'", + os_version, + os_patch, + vendor_patch, + err, + want_err + ); + } + } +} diff --git a/libs/rust/hal/src/hal.rs b/libs/rust/hal/src/hal.rs new file mode 100644 index 0000000..e9f216d --- /dev/null +++ b/libs/rust/hal/src/hal.rs @@ -0,0 +1,772 @@ +// Copyright 2022, 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. + +//! Code for dealing with HAL-defined types, especially conversions to/from internal types. +//! +//! The internal code for KeyMint uses its own type definitions, not the HAL-defined autogenerated +//! types, for two reasons: +//! +//! - The auto-generated types impose a dependency on Binder which is not appropriate for +//! code being built for a secure environment. +//! - The auto-generated types are not idiomatic Rust, and have reduced type safety. +//! +//! This module includes code to convert between HAL types (re-used under `kmr_hal::hal`) and +//! internal types (under `kmr_wire`), via the [`Fromm`] / [`TryFromm`], [`Innto`] and +//! [`TryInnto`] traits (which are deliberately misspelled to avoid a clash with standard +//! traits -- see below). +//! +//! - Going from wire=>HAL is an infallible conversion, as the wire types are stricter. +//! - Going from HAL=>wire is often a fallible conversion, as there may be "enum" values +//! that are not in range. +//! +//! This module (and `kmr_wire`) must be kept in sync with the Android KeyMint HAL definition. + +#![allow(non_snake_case)] + +use crate::binder; +use keymint::{KeyParameterValue::KeyParameterValue, Tag::Tag, TagType::TagType}; +use kmr_wire as wire; +use kmr_wire::{keymint::DateTime, keymint::KeyParam, KeySizeInBits, RsaExponent}; +use log::{error, warn}; +use std::convert::TryFrom; +use std::ffi::CString; + +pub use android_hardware_security_keymint::aidl::android::hardware::security::keymint; +pub use android_hardware_security_rkp::aidl::android::hardware::security::keymint as rkp; +pub use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock; +pub use android_hardware_security_sharedsecret::aidl::android::hardware::security::sharedsecret; + +#[cfg(test)] +mod tests; + +/// Emit a failure for a failed type conversion. +#[inline] +pub fn failed_conversion(err: wire::ValueNotRecognized) -> binder::Status { + // If conversion from a HAL type failed because an enum value was unrecognized, try to use a + // more specific error code. + let errcode = match err { + wire::ValueNotRecognized::KeyPurpose => keymint::ErrorCode::ErrorCode::UNSUPPORTED_PURPOSE, + wire::ValueNotRecognized::Algorithm => keymint::ErrorCode::ErrorCode::UNSUPPORTED_ALGORITHM, + wire::ValueNotRecognized::BlockMode => { + keymint::ErrorCode::ErrorCode::UNSUPPORTED_BLOCK_MODE + } + wire::ValueNotRecognized::PaddingMode => { + keymint::ErrorCode::ErrorCode::UNSUPPORTED_PADDING_MODE + } + wire::ValueNotRecognized::Digest => keymint::ErrorCode::ErrorCode::UNSUPPORTED_DIGEST, + wire::ValueNotRecognized::KeyFormat => { + keymint::ErrorCode::ErrorCode::UNSUPPORTED_KEY_FORMAT + } + wire::ValueNotRecognized::EcCurve => keymint::ErrorCode::ErrorCode::UNSUPPORTED_EC_CURVE, + _ => keymint::ErrorCode::ErrorCode::INVALID_ARGUMENT, + }; + binder::Status::new_service_specific_error( + errcode.0, + Some(&CString::new("conversion from HAL type to internal type failed").unwrap()), + ) +} + +/// Determine the tag type for a tag, based on the top 4 bits of the tag number. +pub fn tag_type(tag: Tag) -> TagType { + match ((tag.0 as u32) & 0xf0000000u32) as i32 { + x if x == TagType::ENUM.0 => TagType::ENUM, + x if x == TagType::ENUM_REP.0 => TagType::ENUM_REP, + x if x == TagType::UINT.0 => TagType::UINT, + x if x == TagType::UINT_REP.0 => TagType::UINT_REP, + x if x == TagType::ULONG.0 => TagType::ULONG, + x if x == TagType::DATE.0 => TagType::DATE, + x if x == TagType::BOOL.0 => TagType::BOOL, + x if x == TagType::BIGNUM.0 => TagType::BIGNUM, + x if x == TagType::BYTES.0 => TagType::BYTES, + x if x == TagType::ULONG_REP.0 => TagType::ULONG_REP, + _ => TagType::INVALID, + } +} + +// Neither the `kmr_wire` types nor the `hal` types are local to this crate, which means that Rust's +// orphan rule means we cannot implement the standard conversion traits. So instead define our own +// equivalent conversion traits that are local, and for which we're allowed to provide +// implementations. Give them an odd name to avoid confusion with the standard traits. + +/// Local equivalent of `From` trait, with a different name to avoid clashes. +pub trait Fromm: Sized { + /// Convert `val` into type `Self`. + fn fromm(val: T) -> Self; +} +/// Local equivalent of `TryFrom` trait, with a different name to avoid clashes. +pub trait TryFromm: Sized { + /// Error type emitted on conversion failure. + type Error; + /// Try to convert `val` into type `Self`. + fn try_fromm(val: T) -> Result; +} +/// Local equivalent of `Into` trait, with a different name to avoid clashes. +pub trait Innto { + /// Convert `self` into type `T`. + fn innto(self) -> T; +} +/// Local equivalent of `TryInto` trait, with a different name to avoid clashes. +pub trait TryInnto { + /// Error type emitted on conversion failure. + type Error; + /// Try to convert `self` into type `T`. + fn try_innto(self) -> Result; +} +/// Blanket implementation of `Innto` from `Fromm` +impl Innto for T +where + U: Fromm, +{ + fn innto(self) -> U { + U::fromm(self) + } +} +/// Blanket implementation of `TryInnto` from `TryFromm` +impl TryInnto for T +where + U: TryFromm, +{ + type Error = U::Error; + fn try_innto(self) -> Result { + U::try_fromm(self) + } +} +/// Blanket implementation of `Fromm>` from `Fromm` +impl Fromm> for Vec +where + U: Fromm, +{ + fn fromm(val: Vec) -> Vec { + val.into_iter().map(|t| ::fromm(t)).collect() + } +} + +// Conversions from `kmr_wire` types into the equivalent types in the auto-generated HAL code. These +// conversions are infallible, because the range of the `wire` types is strictly contained within +// the HAL types. + +impl Fromm + for sharedsecret::SharedSecretParameters::SharedSecretParameters +{ + fn fromm(val: wire::sharedsecret::SharedSecretParameters) -> Self { + Self { seed: val.seed, nonce: val.nonce } + } +} +impl Fromm for secureclock::Timestamp::Timestamp { + fn fromm(val: wire::secureclock::Timestamp) -> Self { + Self { milliSeconds: val.milliseconds } + } +} +impl Fromm for secureclock::TimeStampToken::TimeStampToken { + fn fromm(val: wire::secureclock::TimeStampToken) -> Self { + Self { challenge: val.challenge, timestamp: val.timestamp.innto(), mac: val.mac } + } +} +impl Fromm for keymint::Certificate::Certificate { + fn fromm(val: wire::keymint::Certificate) -> Self { + Self { encodedCertificate: val.encoded_certificate } + } +} +impl Fromm for rkp::DeviceInfo::DeviceInfo { + fn fromm(val: wire::rpc::DeviceInfo) -> Self { + Self { deviceInfo: val.device_info } + } +} +impl Fromm for keymint::HardwareAuthToken::HardwareAuthToken { + fn fromm(val: wire::keymint::HardwareAuthToken) -> Self { + Self { + challenge: val.challenge, + userId: val.user_id, + authenticatorId: val.authenticator_id, + authenticatorType: val.authenticator_type.innto(), + timestamp: val.timestamp.innto(), + mac: val.mac, + } + } +} +impl Fromm for keymint::KeyCharacteristics::KeyCharacteristics { + fn fromm(val: wire::keymint::KeyCharacteristics) -> Self { + Self { + securityLevel: val.security_level.innto(), + authorizations: val.authorizations.innto(), + } + } +} +impl Fromm for keymint::KeyCreationResult::KeyCreationResult { + fn fromm(val: wire::keymint::KeyCreationResult) -> Self { + Self { + keyBlob: val.key_blob, + keyCharacteristics: val.key_characteristics.innto(), + certificateChain: val.certificate_chain.innto(), + } + } +} +impl Fromm + for keymint::KeyMintHardwareInfo::KeyMintHardwareInfo +{ + fn fromm(val: wire::keymint::KeyMintHardwareInfo) -> Self { + Self { + versionNumber: val.version_number, + securityLevel: val.security_level.innto(), + keyMintName: val.key_mint_name, + keyMintAuthorName: val.key_mint_author_name, + timestampTokenRequired: val.timestamp_token_required, + } + } +} +impl Fromm for rkp::MacedPublicKey::MacedPublicKey { + fn fromm(val: wire::rpc::MacedPublicKey) -> Self { + Self { macedKey: val.maced_key } + } +} +impl Fromm for rkp::ProtectedData::ProtectedData { + fn fromm(val: wire::rpc::ProtectedData) -> Self { + Self { protectedData: val.protected_data } + } +} +impl Fromm for rkp::RpcHardwareInfo::RpcHardwareInfo { + fn fromm(val: wire::rpc::HardwareInfo) -> Self { + Self { + versionNumber: val.version_number, + rpcAuthorName: val.rpc_author_name, + supportedEekCurve: val.supported_eek_curve as i32, + uniqueId: val.unique_id, + supportedNumKeysInCsr: val.supported_num_keys_in_csr, + } + } +} + +impl Fromm for keymint::KeyParameter::KeyParameter { + fn fromm(val: wire::keymint::KeyParam) -> Self { + let (tag, value) = match val { + // Enum-holding variants. + KeyParam::Purpose(v) => (Tag::PURPOSE, KeyParameterValue::KeyPurpose(v.innto())), + KeyParam::Algorithm(v) => (Tag::ALGORITHM, KeyParameterValue::Algorithm(v.innto())), + KeyParam::BlockMode(v) => (Tag::BLOCK_MODE, KeyParameterValue::BlockMode(v.innto())), + KeyParam::Digest(v) => (Tag::DIGEST, KeyParameterValue::Digest(v.innto())), + KeyParam::Padding(v) => (Tag::PADDING, KeyParameterValue::PaddingMode(v.innto())), + KeyParam::EcCurve(v) => (Tag::EC_CURVE, KeyParameterValue::EcCurve(v.innto())), + KeyParam::RsaOaepMgfDigest(v) => { + (Tag::RSA_OAEP_MGF_DIGEST, KeyParameterValue::Digest(v.innto())) + } + KeyParam::Origin(v) => (Tag::ORIGIN, KeyParameterValue::Origin(v.innto())), + + // `u32`-holding variants. + KeyParam::KeySize(v) => (Tag::KEY_SIZE, KeyParameterValue::Integer(v.0 as i32)), + KeyParam::MinMacLength(v) => { + (Tag::MIN_MAC_LENGTH, KeyParameterValue::Integer(v as i32)) + } + KeyParam::MaxUsesPerBoot(v) => { + (Tag::MAX_USES_PER_BOOT, KeyParameterValue::Integer(v as i32)) + } + KeyParam::UsageCountLimit(v) => { + (Tag::USAGE_COUNT_LIMIT, KeyParameterValue::Integer(v as i32)) + } + KeyParam::UserId(v) => (Tag::USER_ID, KeyParameterValue::Integer(v as i32)), + KeyParam::UserAuthType(v) => { + // Special case: auth type is a bitmask, so the Rust types use `u32` but the HAL + // type has an "enum". + ( + Tag::USER_AUTH_TYPE, + KeyParameterValue::HardwareAuthenticatorType( + keymint::HardwareAuthenticatorType::HardwareAuthenticatorType(v as i32), + ), + ) + } + KeyParam::AuthTimeout(v) => (Tag::AUTH_TIMEOUT, KeyParameterValue::Integer(v as i32)), + KeyParam::OsVersion(v) => (Tag::OS_VERSION, KeyParameterValue::Integer(v as i32)), + KeyParam::OsPatchlevel(v) => (Tag::OS_PATCHLEVEL, KeyParameterValue::Integer(v as i32)), + KeyParam::VendorPatchlevel(v) => { + (Tag::VENDOR_PATCHLEVEL, KeyParameterValue::Integer(v as i32)) + } + KeyParam::BootPatchlevel(v) => { + (Tag::BOOT_PATCHLEVEL, KeyParameterValue::Integer(v as i32)) + } + KeyParam::MacLength(v) => (Tag::MAC_LENGTH, KeyParameterValue::Integer(v as i32)), + KeyParam::MaxBootLevel(v) => { + (Tag::MAX_BOOT_LEVEL, KeyParameterValue::Integer(v as i32)) + } + + // `u64`-holding variants. + KeyParam::RsaPublicExponent(v) => { + (Tag::RSA_PUBLIC_EXPONENT, KeyParameterValue::LongInteger(v.0 as i64)) + } + KeyParam::UserSecureId(v) => { + (Tag::USER_SECURE_ID, KeyParameterValue::LongInteger(v as i64)) + } + + // `true`-holding variants. + KeyParam::CallerNonce => (Tag::CALLER_NONCE, KeyParameterValue::BoolValue(true)), + KeyParam::IncludeUniqueId => { + (Tag::INCLUDE_UNIQUE_ID, KeyParameterValue::BoolValue(true)) + } + KeyParam::BootloaderOnly => (Tag::BOOTLOADER_ONLY, KeyParameterValue::BoolValue(true)), + KeyParam::RollbackResistance => { + (Tag::ROLLBACK_RESISTANCE, KeyParameterValue::BoolValue(true)) + } + KeyParam::EarlyBootOnly => (Tag::EARLY_BOOT_ONLY, KeyParameterValue::BoolValue(true)), + KeyParam::AllowWhileOnBody => { + (Tag::ALLOW_WHILE_ON_BODY, KeyParameterValue::BoolValue(true)) + } + KeyParam::NoAuthRequired => (Tag::NO_AUTH_REQUIRED, KeyParameterValue::BoolValue(true)), + KeyParam::TrustedUserPresenceRequired => { + (Tag::TRUSTED_USER_PRESENCE_REQUIRED, KeyParameterValue::BoolValue(true)) + } + KeyParam::TrustedConfirmationRequired => { + (Tag::TRUSTED_CONFIRMATION_REQUIRED, KeyParameterValue::BoolValue(true)) + } + KeyParam::UnlockedDeviceRequired => { + (Tag::UNLOCKED_DEVICE_REQUIRED, KeyParameterValue::BoolValue(true)) + } + KeyParam::DeviceUniqueAttestation => { + (Tag::DEVICE_UNIQUE_ATTESTATION, KeyParameterValue::BoolValue(true)) + } + KeyParam::StorageKey => (Tag::STORAGE_KEY, KeyParameterValue::BoolValue(true)), + KeyParam::ResetSinceIdRotation => { + (Tag::RESET_SINCE_ID_ROTATION, KeyParameterValue::BoolValue(true)) + } + + // `DateTime`-holding variants. + KeyParam::ActiveDatetime(v) => { + (Tag::ACTIVE_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch)) + } + KeyParam::OriginationExpireDatetime(v) => { + (Tag::ORIGINATION_EXPIRE_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch)) + } + KeyParam::UsageExpireDatetime(v) => { + (Tag::USAGE_EXPIRE_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch)) + } + KeyParam::CreationDatetime(v) => { + (Tag::CREATION_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch)) + } + KeyParam::CertificateNotBefore(v) => { + (Tag::CERTIFICATE_NOT_BEFORE, KeyParameterValue::DateTime(v.ms_since_epoch)) + } + KeyParam::CertificateNotAfter(v) => { + (Tag::CERTIFICATE_NOT_AFTER, KeyParameterValue::DateTime(v.ms_since_epoch)) + } + + // `Vec`-holding variants. + KeyParam::ApplicationId(v) => (Tag::APPLICATION_ID, KeyParameterValue::Blob(v)), + KeyParam::ApplicationData(v) => (Tag::APPLICATION_DATA, KeyParameterValue::Blob(v)), + KeyParam::AttestationChallenge(v) => { + (Tag::ATTESTATION_CHALLENGE, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationApplicationId(v) => { + (Tag::ATTESTATION_APPLICATION_ID, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdBrand(v) => { + (Tag::ATTESTATION_ID_BRAND, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdDevice(v) => { + (Tag::ATTESTATION_ID_DEVICE, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdProduct(v) => { + (Tag::ATTESTATION_ID_PRODUCT, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdSerial(v) => { + (Tag::ATTESTATION_ID_SERIAL, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdImei(v) => { + (Tag::ATTESTATION_ID_IMEI, KeyParameterValue::Blob(v)) + } + #[cfg(feature = "hal_v3")] + KeyParam::AttestationIdSecondImei(v) => { + (Tag::ATTESTATION_ID_SECOND_IMEI, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdMeid(v) => { + (Tag::ATTESTATION_ID_MEID, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdManufacturer(v) => { + (Tag::ATTESTATION_ID_MANUFACTURER, KeyParameterValue::Blob(v)) + } + KeyParam::AttestationIdModel(v) => { + (Tag::ATTESTATION_ID_MODEL, KeyParameterValue::Blob(v)) + } + KeyParam::Nonce(v) => (Tag::NONCE, KeyParameterValue::Blob(v)), + KeyParam::RootOfTrust(v) => (Tag::ROOT_OF_TRUST, KeyParameterValue::Blob(v)), + KeyParam::CertificateSerial(v) => (Tag::CERTIFICATE_SERIAL, KeyParameterValue::Blob(v)), + KeyParam::CertificateSubject(v) => { + (Tag::CERTIFICATE_SUBJECT, KeyParameterValue::Blob(v)) + } + #[cfg(feature = "hal_v4")] + KeyParam::ModuleHash(v) => (Tag::MODULE_HASH, KeyParameterValue::Blob(v)), + }; + Self { tag, value } + } +} + +// Conversions from auto-generated HAL types into the equivalent types from `kmr_wire`. These +// conversions are generally fallible, because the "enum" types generated for the HAL are actually +// `i32` values, which may contain invalid values. + +impl Fromm for wire::secureclock::TimeStampToken { + fn fromm(val: secureclock::TimeStampToken::TimeStampToken) -> Self { + Self { challenge: val.challenge, timestamp: val.timestamp.innto(), mac: val.mac } + } +} +impl Fromm for wire::secureclock::Timestamp { + fn fromm(val: secureclock::Timestamp::Timestamp) -> Self { + Self { milliseconds: val.milliSeconds } + } +} +impl Fromm + for wire::sharedsecret::SharedSecretParameters +{ + fn fromm(val: sharedsecret::SharedSecretParameters::SharedSecretParameters) -> Self { + Self { seed: val.seed, nonce: val.nonce } + } +} +impl TryFromm for wire::keymint::AttestationKey { + type Error = wire::ValueNotRecognized; + fn try_fromm(val: keymint::AttestationKey::AttestationKey) -> Result { + Ok(Self { + key_blob: val.keyBlob, + attest_key_params: val + .attestKeyParams // Vec + .into_iter() // Iter + .filter_map(|p| (&p).try_innto().transpose()) + .collect::, _>>()?, + issuer_subject_name: val.issuerSubjectName, + }) + } +} +impl TryFromm for wire::keymint::HardwareAuthToken { + type Error = wire::ValueNotRecognized; + fn try_fromm(val: keymint::HardwareAuthToken::HardwareAuthToken) -> Result { + Ok(Self { + challenge: val.challenge, + user_id: val.userId, + authenticator_id: val.authenticatorId, + authenticator_type: val.authenticatorType.try_innto()?, + timestamp: val.timestamp.innto(), + mac: val.mac, + }) + } +} +impl Fromm for wire::rpc::MacedPublicKey { + fn fromm(val: rkp::MacedPublicKey::MacedPublicKey) -> Self { + Self { maced_key: val.macedKey } + } +} +impl Fromm<&rkp::MacedPublicKey::MacedPublicKey> for wire::rpc::MacedPublicKey { + fn fromm(val: &rkp::MacedPublicKey::MacedPublicKey) -> Self { + Self { maced_key: val.macedKey.to_vec() } + } +} + +macro_rules! value_of { + { + $val:expr, $variant:ident + } => { + if let keymint::KeyParameterValue::KeyParameterValue::$variant(v) = $val.value { + Ok(v) + } else { + error!("failed to convert parameter '{}' with value {:?}", stringify!($val), $val); + Err(wire::ValueNotRecognized::$variant) + } + } +} + +macro_rules! check_bool { + { + $val:expr + } => { + if let keymint::KeyParameterValue::KeyParameterValue::BoolValue(true) = $val.value { + Ok(()) + } else { + Err(wire::ValueNotRecognized::Bool) + } + } +} + +macro_rules! clone_blob { + { + $val:expr + } => { + if let keymint::KeyParameterValue::KeyParameterValue::Blob(b) = &$val.value { + Ok(b.clone()) + } else { + Err(wire::ValueNotRecognized::Blob) + } + } +} + +/// Converting a HAL `KeyParameter` to a wire `KeyParam` may fail (producing an `Err`) but may also +/// silently drop unknown tags (producing `Ok(None)`) +impl TryFromm<&keymint::KeyParameter::KeyParameter> for Option { + type Error = wire::ValueNotRecognized; + fn try_fromm(val: &keymint::KeyParameter::KeyParameter) -> Result { + Ok(match val.tag { + // Enum-holding variants. + keymint::Tag::Tag::PURPOSE => { + Some(KeyParam::Purpose(value_of!(val, KeyPurpose)?.try_innto()?)) + } + keymint::Tag::Tag::ALGORITHM => { + Some(KeyParam::Algorithm(value_of!(val, Algorithm)?.try_innto()?)) + } + keymint::Tag::Tag::BLOCK_MODE => { + Some(KeyParam::BlockMode(value_of!(val, BlockMode)?.try_innto()?)) + } + keymint::Tag::Tag::DIGEST => { + Some(KeyParam::Digest(value_of!(val, Digest)?.try_innto()?)) + } + keymint::Tag::Tag::PADDING => { + Some(KeyParam::Padding(value_of!(val, PaddingMode)?.try_innto()?)) + } + keymint::Tag::Tag::EC_CURVE => { + Some(KeyParam::EcCurve(value_of!(val, EcCurve)?.try_innto()?)) + } + keymint::Tag::Tag::RSA_OAEP_MGF_DIGEST => { + Some(KeyParam::RsaOaepMgfDigest(value_of!(val, Digest)?.try_innto()?)) + } + keymint::Tag::Tag::ORIGIN => { + Some(KeyParam::Origin(value_of!(val, Origin)?.try_innto()?)) + } + + // Special case: although `Tag::USER_AUTH_TYPE` claims to have an associated enum, it's + // actually a bitmask rather than an enum. + keymint::Tag::Tag::USER_AUTH_TYPE => { + let val = value_of!(val, HardwareAuthenticatorType)?; + Some(KeyParam::UserAuthType(val.0 as u32)) + } + + // `u32`-holding variants. + keymint::Tag::Tag::KEY_SIZE => { + Some(KeyParam::KeySize(KeySizeInBits(value_of!(val, Integer)? as u32))) + } + keymint::Tag::Tag::MIN_MAC_LENGTH => { + Some(KeyParam::MinMacLength(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::MAX_USES_PER_BOOT => { + Some(KeyParam::MaxUsesPerBoot(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::USAGE_COUNT_LIMIT => { + Some(KeyParam::UsageCountLimit(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::USER_ID => Some(KeyParam::UserId(value_of!(val, Integer)? as u32)), + keymint::Tag::Tag::AUTH_TIMEOUT => { + Some(KeyParam::AuthTimeout(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::OS_VERSION => { + Some(KeyParam::OsVersion(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::OS_PATCHLEVEL => { + Some(KeyParam::OsPatchlevel(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::VENDOR_PATCHLEVEL => { + Some(KeyParam::VendorPatchlevel(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::BOOT_PATCHLEVEL => { + Some(KeyParam::BootPatchlevel(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::MAC_LENGTH => { + Some(KeyParam::MacLength(value_of!(val, Integer)? as u32)) + } + keymint::Tag::Tag::MAX_BOOT_LEVEL => { + Some(KeyParam::MaxBootLevel(value_of!(val, Integer)? as u32)) + } + + // `u64`-holding variants. + keymint::Tag::Tag::RSA_PUBLIC_EXPONENT => { + Some(KeyParam::RsaPublicExponent(RsaExponent(value_of!(val, LongInteger)? as u64))) + } + keymint::Tag::Tag::USER_SECURE_ID => { + Some(KeyParam::UserSecureId(value_of!(val, LongInteger)? as u64)) + } + + // `bool`-holding variants; only `true` is allowed. + keymint::Tag::Tag::CALLER_NONCE => { + check_bool!(val)?; + Some(KeyParam::CallerNonce) + } + keymint::Tag::Tag::INCLUDE_UNIQUE_ID => { + check_bool!(val)?; + Some(KeyParam::IncludeUniqueId) + } + keymint::Tag::Tag::BOOTLOADER_ONLY => { + check_bool!(val)?; + Some(KeyParam::BootloaderOnly) + } + keymint::Tag::Tag::ROLLBACK_RESISTANCE => { + check_bool!(val)?; + Some(KeyParam::RollbackResistance) + } + keymint::Tag::Tag::EARLY_BOOT_ONLY => { + check_bool!(val)?; + Some(KeyParam::EarlyBootOnly) + } + keymint::Tag::Tag::NO_AUTH_REQUIRED => { + check_bool!(val)?; + Some(KeyParam::NoAuthRequired) + } + keymint::Tag::Tag::ALLOW_WHILE_ON_BODY => { + check_bool!(val)?; + Some(KeyParam::AllowWhileOnBody) + } + keymint::Tag::Tag::TRUSTED_USER_PRESENCE_REQUIRED => { + check_bool!(val)?; + Some(KeyParam::TrustedUserPresenceRequired) + } + keymint::Tag::Tag::TRUSTED_CONFIRMATION_REQUIRED => { + check_bool!(val)?; + Some(KeyParam::TrustedConfirmationRequired) + } + keymint::Tag::Tag::UNLOCKED_DEVICE_REQUIRED => { + check_bool!(val)?; + Some(KeyParam::UnlockedDeviceRequired) + } + keymint::Tag::Tag::DEVICE_UNIQUE_ATTESTATION => { + check_bool!(val)?; + Some(KeyParam::DeviceUniqueAttestation) + } + keymint::Tag::Tag::STORAGE_KEY => { + check_bool!(val)?; + Some(KeyParam::StorageKey) + } + keymint::Tag::Tag::RESET_SINCE_ID_ROTATION => { + check_bool!(val)?; + Some(KeyParam::ResetSinceIdRotation) + } + + // `DateTime`-holding variants. + keymint::Tag::Tag::ACTIVE_DATETIME => Some(KeyParam::ActiveDatetime(DateTime { + ms_since_epoch: value_of!(val, DateTime)?, + })), + keymint::Tag::Tag::ORIGINATION_EXPIRE_DATETIME => { + Some(KeyParam::OriginationExpireDatetime(DateTime { + ms_since_epoch: value_of!(val, DateTime)?, + })) + } + keymint::Tag::Tag::USAGE_EXPIRE_DATETIME => { + Some(KeyParam::UsageExpireDatetime(DateTime { + ms_since_epoch: value_of!(val, DateTime)?, + })) + } + keymint::Tag::Tag::CREATION_DATETIME => Some(KeyParam::CreationDatetime(DateTime { + ms_since_epoch: value_of!(val, DateTime)?, + })), + keymint::Tag::Tag::CERTIFICATE_NOT_BEFORE => { + Some(KeyParam::CertificateNotBefore(DateTime { + ms_since_epoch: value_of!(val, DateTime)?, + })) + } + keymint::Tag::Tag::CERTIFICATE_NOT_AFTER => { + Some(KeyParam::CertificateNotAfter(DateTime { + ms_since_epoch: value_of!(val, DateTime)?, + })) + } + + // `Vec`-holding variants. + keymint::Tag::Tag::APPLICATION_ID => Some(KeyParam::ApplicationId(clone_blob!(val)?)), + keymint::Tag::Tag::APPLICATION_DATA => { + Some(KeyParam::ApplicationData(clone_blob!(val)?)) + } + keymint::Tag::Tag::ROOT_OF_TRUST => Some(KeyParam::RootOfTrust(clone_blob!(val)?)), + keymint::Tag::Tag::ATTESTATION_CHALLENGE => { + Some(KeyParam::AttestationChallenge(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_APPLICATION_ID => { + Some(KeyParam::AttestationApplicationId(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_BRAND => { + Some(KeyParam::AttestationIdBrand(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_DEVICE => { + Some(KeyParam::AttestationIdDevice(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_PRODUCT => { + Some(KeyParam::AttestationIdProduct(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_SERIAL => { + Some(KeyParam::AttestationIdSerial(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_IMEI => { + Some(KeyParam::AttestationIdImei(clone_blob!(val)?)) + } + #[cfg(feature = "hal_v3")] + keymint::Tag::Tag::ATTESTATION_ID_SECOND_IMEI => { + Some(KeyParam::AttestationIdSecondImei(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_MEID => { + Some(KeyParam::AttestationIdMeid(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_MANUFACTURER => { + Some(KeyParam::AttestationIdManufacturer(clone_blob!(val)?)) + } + keymint::Tag::Tag::ATTESTATION_ID_MODEL => { + Some(KeyParam::AttestationIdModel(clone_blob!(val)?)) + } + keymint::Tag::Tag::NONCE => Some(KeyParam::Nonce(clone_blob!(val)?)), + keymint::Tag::Tag::CERTIFICATE_SERIAL => { + Some(KeyParam::CertificateSerial(clone_blob!(val)?)) + } + keymint::Tag::Tag::CERTIFICATE_SUBJECT => { + Some(KeyParam::CertificateSubject(clone_blob!(val)?)) + } + #[cfg(feature = "hal_v4")] + keymint::Tag::Tag::MODULE_HASH => Some(KeyParam::ModuleHash(clone_blob!(val)?)), + + // Unsupported variants + keymint::Tag::Tag::UNIQUE_ID + | keymint::Tag::Tag::HARDWARE_TYPE + | keymint::Tag::Tag::MIN_SECONDS_BETWEEN_OPS + | keymint::Tag::Tag::IDENTITY_CREDENTIAL_KEY + | keymint::Tag::Tag::ASSOCIATED_DATA + | keymint::Tag::Tag::CONFIRMATION_TOKEN => { + error!("Unsupported tag {:?} encountered", val.tag); + return Err(wire::ValueNotRecognized::Tag); + } + _ => { + warn!("Unknown tag {:?} silently dropped", val.tag); + None + } + }) + } +} + +/// Macro that emits conversion implementations for `wire` and HAL enums. +/// - The `hal::keymint` version of the enum is a newtype holding `i32` +/// - The `wire::keymint` version of the enum is an exhaustive enum with `[repr(i32)]` +macro_rules! enum_convert { + { + $wenum:ty => $henum:ty + } => { + impl Fromm<$wenum> for $henum { + fn fromm(val: $wenum) -> Self { + Self(val as i32) + } + } + impl TryFromm<$henum> for $wenum { + type Error = wire::ValueNotRecognized; + fn try_fromm(val: $henum) -> Result { + Self::try_from(val.0) + } + } + }; +} +enum_convert! { wire::keymint::ErrorCode => keymint::ErrorCode::ErrorCode } +enum_convert! { wire::keymint::Algorithm => keymint::Algorithm::Algorithm } +enum_convert! { wire::keymint::BlockMode => keymint::BlockMode::BlockMode } +enum_convert! { wire::keymint::Digest => keymint::Digest::Digest } +enum_convert! { wire::keymint::EcCurve => keymint::EcCurve::EcCurve } +enum_convert! { wire::keymint::HardwareAuthenticatorType => +keymint::HardwareAuthenticatorType::HardwareAuthenticatorType } +enum_convert! { wire::keymint::KeyFormat => keymint::KeyFormat::KeyFormat } +enum_convert! { wire::keymint::KeyOrigin => keymint::KeyOrigin::KeyOrigin } +enum_convert! { wire::keymint::KeyPurpose => keymint::KeyPurpose::KeyPurpose } +enum_convert! { wire::keymint::PaddingMode => keymint::PaddingMode::PaddingMode } +enum_convert! { wire::keymint::SecurityLevel => keymint::SecurityLevel::SecurityLevel } +enum_convert! { wire::keymint::Tag => keymint::Tag::Tag } +enum_convert! { wire::keymint::TagType => keymint::TagType::TagType } diff --git a/libs/rust/hal/src/hal/tests.rs b/libs/rust/hal/src/hal/tests.rs new file mode 100644 index 0000000..9f419fb --- /dev/null +++ b/libs/rust/hal/src/hal/tests.rs @@ -0,0 +1,165 @@ +// Copyright 2022, 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. + +use crate::cbor::value::Value; +use kmr_derive::AsCborValue; +use kmr_wire::{cbor_type_error, AsCborValue, CborError}; + +#[derive(Debug, Clone, PartialEq, Eq, AsCborValue)] +struct Timestamp { + milliseconds: i64, +} + +#[derive(Debug, Clone, PartialEq, Eq, AsCborValue)] +struct NamedFields { + challenge: i64, + timestamp: Timestamp, + mac: Vec, +} + +#[test] +fn test_cbor_value_cddl() { + assert_eq!(::cddl_typename().unwrap(), "NamedFields"); + assert_eq!( + ::cddl_schema().unwrap(), + r#"[ + challenge: int, + timestamp: Timestamp, + mac: bstr, +]"# + ); +} + +#[test] +fn test_cbor_value_roundtrip() { + let obj = NamedFields { + challenge: 42, + timestamp: Timestamp { milliseconds: 10_000_000 }, + mac: vec![1, 2, 3, 4], + }; + + let obj_val = obj.clone().to_cbor_value().unwrap(); + let recovered_obj = ::from_cbor_value(obj_val).unwrap(); + assert_eq!(obj, recovered_obj); +} + +#[test] +fn test_cbor_parse_fail() { + let tests = vec![ + (Value::Map(vec![]), "expected arr"), + (Value::Integer(0.into()), "expected arr"), + (Value::Array(vec![]), "expected arr len 3"), + ( + Value::Array(vec![ + Value::Integer(0.into()), + Value::Integer(0.into()), + Value::Integer(0.into()), + Value::Integer(0.into()), + ]), + "expected arr len 3", + ), + ( + Value::Array(vec![ + Value::Integer(0.into()), + Value::Array(vec![Value::Integer(0.into())]), + Value::Integer(0.into()), + ]), + "expected bstr", + ), + ( + Value::Array(vec![ + Value::Integer(0.into()), + Value::Array(vec![Value::Integer(0.into()), Value::Integer(0.into())]), + Value::Bytes(vec![1, 2, 3]), + ]), + "expected arr len 1", + ), + ]; + for (val, wanterr) in tests { + let result = ::from_cbor_value(val); + expect_err(result, wanterr); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, AsCborValue)] +struct UnnamedFields(i64, Timestamp); + +#[test] +fn test_unnamed_cbor_value_cddl() { + assert_eq!(::cddl_typename().unwrap(), "UnnamedFields"); + assert_eq!( + ::cddl_schema().unwrap(), + r#"[ + int, + Timestamp, +]"# + ); +} + +#[test] +fn test_unnamed_cbor_value_roundtrip() { + let obj = UnnamedFields(42, Timestamp { milliseconds: 10_000_000 }); + + let obj_val = obj.clone().to_cbor_value().unwrap(); + let recovered_obj = ::from_cbor_value(obj_val).unwrap(); + assert_eq!(obj, recovered_obj); +} + +#[test] +fn test_unnamed_cbor_parse_fail() { + let tests = vec![ + (Value::Map(vec![]), "expected arr"), + (Value::Integer(0.into()), "expected arr"), + (Value::Array(vec![]), "expected arr len 2"), + ( + Value::Array(vec![ + Value::Integer(0.into()), + Value::Integer(0.into()), + Value::Integer(0.into()), + ]), + "expected arr len 2", + ), + ( + Value::Array(vec![ + Value::Bytes(vec![1, 2, 3]), + Value::Array(vec![Value::Integer(0.into())]), + ]), + "expected i64", + ), + ( + Value::Array(vec![ + Value::Integer(0.into()), + Value::Array(vec![Value::Integer(0.into()), Value::Integer(0.into())]), + ]), + "expected arr len 1", + ), + ]; + for (val, wanterr) in tests { + let result = ::from_cbor_value(val); + expect_err(result, wanterr); + } +} + +/// Check for an expected error. +#[cfg(test)] +pub fn expect_err(result: Result, err_msg: &str) { + assert!(result.is_err(), "unexpected success; wanted error containing '{}'", err_msg); + let err = result.err(); + assert!( + format!("{:?}", err).contains(err_msg), + "unexpected error {:?}, doesn't contain '{}'", + err, + err_msg + ); +} diff --git a/libs/rust/hal/src/keymint.rs b/libs/rust/hal/src/keymint.rs new file mode 100644 index 0000000..7fe8518 --- /dev/null +++ b/libs/rust/hal/src/keymint.rs @@ -0,0 +1,500 @@ +// Copyright 2022, 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. + +//! KeyMint HAL device implementation. + +use crate::binder; +use crate::hal::{ + failed_conversion, keymint, keymint::IKeyMintOperation::IKeyMintOperation, + secureclock::TimeStampToken::TimeStampToken, Innto, TryInnto, +}; +use crate::{ChannelHalService, SerializedChannel}; +use kmr_wire::{keymint::KeyParam, AsCborValue, *}; +use log::warn; +use std::ffi::CString; +use std::{ + ops::DerefMut, + sync::{Arc, Mutex, MutexGuard, RwLock}, +}; + +/// Maximum overhead size from CBOR serialization of operation messages. +/// +/// A serialized `FinishRequest` includes the following additional bytes over and +/// above the size of the input (at most): +/// - 1: array wrapper (0x86) +/// - 9: int (0x1b + u64) [op_handle] +/// - 1: array wrapper (0x81) [input] +/// - 9: input data length +/// - XX: input data +/// - 1: array wrapper (0x81) [signature] +/// - 5: signature data length +/// - 132: signature data (P-521 point) +/// - 1: array wrapper (0x81) [auth_token] +/// - 9: int (0x1b + u64) [challenge] +/// - 9: int (0x1b + u64) [user_id] +/// - 9: int (0x1b + u64) [authenticator_id] +/// - 9: int (0x1b + u64) [authenticator_type] +/// - 1: array wrapper (0x81)[timestamp] +/// - 9: int (0x1b + u64) [user_id] +/// - 2: bstr header [mac] +/// - 32: bstr [mac] +/// - 1: array wrapper (0x81) [timestamp_token] +/// - 1: array wrapper [TimeStampToken] +/// - 9: int (0x1b + u64) [challenge] +/// - 1: array wrapper (0x81)[timestamp] +/// - 9: int (0x1b + u64) [user_id] +/// - 2: bstr header [mac] +/// - 32: bstr [mac] +/// - 1: array wrapper (0x81) [confirmation_token] +/// - 2: bstr header [confirmation token] +/// - 32: bstr [confirmation token (HMAC-SHA256)] +/// +/// Add some leeway in case encodings change. +pub const MAX_CBOR_OVERHEAD: usize = 350; + +/// IKeyMintDevice implementation which converts all method invocations to serialized +/// requests that are sent down the associated channel. +pub struct Device { + channel: Arc>, +} + +impl Device { + /// Construct a new instance that uses the provided channel. + pub fn new(channel: Arc>) -> Self { + Self { channel } + } + + /// Create a new instance wrapped in a proxy object. + pub fn new_as_binder( + channel: Arc>, + ) -> binder::Strong { + keymint::IKeyMintDevice::BnKeyMintDevice::new_binder( + Self::new(channel), + binder::BinderFeatures::default(), + ) + } +} + +impl ChannelHalService for Device { + fn channel(&self) -> MutexGuard { + self.channel.lock().unwrap() + } +} + +impl binder::Interface for Device {} + +impl keymint::IKeyMintDevice::IKeyMintDevice for Device { + fn getHardwareInfo(&self) -> binder::Result { + let rsp: GetHardwareInfoResponse = self.execute(GetHardwareInfoRequest {})?; + Ok(rsp.ret.innto()) + } + fn addRngEntropy(&self, data: &[u8]) -> binder::Result<()> { + let _rsp: AddRngEntropyResponse = + self.execute(AddRngEntropyRequest { data: data.to_vec() })?; + Ok(()) + } + fn generateKey( + &self, + keyParams: &[keymint::KeyParameter::KeyParameter], + attestationKey: Option<&keymint::AttestationKey::AttestationKey>, + ) -> binder::Result { + let rsp: GenerateKeyResponse = self.execute(GenerateKeyRequest { + key_params: keyParams + .iter() + .filter_map(|p| p.try_innto().transpose()) + .collect::, _>>() + .map_err(failed_conversion)?, + attestation_key: match attestationKey { + None => None, + Some(k) => Some(k.clone().try_innto().map_err(failed_conversion)?), + }, + })?; + Ok(rsp.ret.innto()) + } + fn importKey( + &self, + keyParams: &[keymint::KeyParameter::KeyParameter], + keyFormat: keymint::KeyFormat::KeyFormat, + keyData: &[u8], + attestationKey: Option<&keymint::AttestationKey::AttestationKey>, + ) -> binder::Result { + let rsp: ImportKeyResponse = self.execute(ImportKeyRequest { + key_params: keyParams + .iter() + .filter_map(|p| p.try_innto().transpose()) + .collect::, _>>() + .map_err(failed_conversion)?, + key_format: keyFormat.try_innto().map_err(failed_conversion)?, + key_data: keyData.to_vec(), + attestation_key: match attestationKey { + None => None, + Some(k) => Some(k.clone().try_innto().map_err(failed_conversion)?), + }, + })?; + Ok(rsp.ret.innto()) + } + fn importWrappedKey( + &self, + wrappedKeyData: &[u8], + wrappingKeyBlob: &[u8], + maskingKey: &[u8], + unwrappingParams: &[keymint::KeyParameter::KeyParameter], + passwordSid: i64, + biometricSid: i64, + ) -> binder::Result { + let rsp: ImportWrappedKeyResponse = self.execute(ImportWrappedKeyRequest { + wrapped_key_data: wrappedKeyData.to_vec(), + wrapping_key_blob: wrappingKeyBlob.to_vec(), + masking_key: maskingKey.to_vec(), + unwrapping_params: unwrappingParams + .iter() + .filter_map(|p| p.try_innto().transpose()) + .collect::, _>>() + .map_err(failed_conversion)?, + password_sid: passwordSid, + biometric_sid: biometricSid, + })?; + Ok(rsp.ret.innto()) + } + fn upgradeKey( + &self, + keyBlobToUpgrade: &[u8], + upgradeParams: &[keymint::KeyParameter::KeyParameter], + ) -> binder::Result> { + let rsp: UpgradeKeyResponse = self.execute(UpgradeKeyRequest { + key_blob_to_upgrade: keyBlobToUpgrade.to_vec(), + upgrade_params: upgradeParams + .iter() + .filter_map(|p| p.try_innto().transpose()) + .collect::, _>>() + .map_err(failed_conversion)?, + })?; + Ok(rsp.ret) + } + fn deleteKey(&self, keyBlob: &[u8]) -> binder::Result<()> { + let _rsp: DeleteKeyResponse = + self.execute(DeleteKeyRequest { key_blob: keyBlob.to_vec() })?; + Ok(()) + } + fn deleteAllKeys(&self) -> binder::Result<()> { + let _rsp: DeleteAllKeysResponse = self.execute(DeleteAllKeysRequest {})?; + Ok(()) + } + fn destroyAttestationIds(&self) -> binder::Result<()> { + let _rsp: DestroyAttestationIdsResponse = self.execute(DestroyAttestationIdsRequest {})?; + Ok(()) + } + fn begin( + &self, + purpose: keymint::KeyPurpose::KeyPurpose, + keyBlob: &[u8], + params: &[keymint::KeyParameter::KeyParameter], + authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>, + ) -> binder::Result { + let rsp: BeginResponse = self.execute(BeginRequest { + purpose: purpose.try_innto().map_err(failed_conversion)?, + key_blob: keyBlob.to_vec(), + params: params + .iter() + .filter_map(|p| p.try_innto().transpose()) + .collect::, _>>() + .map_err(failed_conversion)?, + auth_token: match authToken { + None => None, + Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?), + }, + })?; + // The `begin()` method is a special case. + // - Internally, the in-progress operation is identified by an opaque handle value. + // - Externally, the in-progress operation is represented as an `IKeyMintOperation` Binder + // object. + // The `WireOperation` struct contains the former, and acts as the latter. + let op = Operation::new_as_binder(self.channel.clone(), rsp.ret.op_handle); + Ok(keymint::BeginResult::BeginResult { + challenge: rsp.ret.challenge, + params: rsp.ret.params.innto(), + operation: Some(op), + }) + } + fn deviceLocked( + &self, + _passwordOnly: bool, + _timestampToken: Option<&TimeStampToken>, + ) -> binder::Result<()> { + // This method is deprecated and unused, so just fail with error UNIMPLEMENTED. + warn!("Deprecated method devicedLocked() was called"); + Err(binder::Status::new_service_specific_error( + keymint::ErrorCode::ErrorCode::UNIMPLEMENTED.0, + Some(&CString::new("Deprecated method deviceLocked() is not implemented").unwrap()), + )) + } + fn earlyBootEnded(&self) -> binder::Result<()> { + let _rsp: EarlyBootEndedResponse = self.execute(EarlyBootEndedRequest {})?; + Ok(()) + } + fn convertStorageKeyToEphemeral(&self, storageKeyBlob: &[u8]) -> binder::Result> { + let rsp: ConvertStorageKeyToEphemeralResponse = + self.execute(ConvertStorageKeyToEphemeralRequest { + storage_key_blob: storageKeyBlob.to_vec(), + })?; + Ok(rsp.ret) + } + fn getKeyCharacteristics( + &self, + keyBlob: &[u8], + appId: &[u8], + appData: &[u8], + ) -> binder::Result> { + let rsp: GetKeyCharacteristicsResponse = self.execute(GetKeyCharacteristicsRequest { + key_blob: keyBlob.to_vec(), + app_id: appId.to_vec(), + app_data: appData.to_vec(), + })?; + Ok(rsp.ret.innto()) + } + #[cfg(feature = "hal_v2")] + fn getRootOfTrustChallenge(&self) -> binder::Result<[u8; 16]> { + let rsp: GetRootOfTrustChallengeResponse = + self.execute(GetRootOfTrustChallengeRequest {})?; + Ok(rsp.ret) + } + #[cfg(feature = "hal_v2")] + fn getRootOfTrust(&self, challenge: &[u8; 16]) -> binder::Result> { + let rsp: GetRootOfTrustResponse = + self.execute(GetRootOfTrustRequest { challenge: *challenge })?; + Ok(rsp.ret) + } + #[cfg(feature = "hal_v2")] + fn sendRootOfTrust(&self, root_of_trust: &[u8]) -> binder::Result<()> { + let _rsp: SendRootOfTrustResponse = + self.execute(SendRootOfTrustRequest { root_of_trust: root_of_trust.to_vec() })?; + Ok(()) + } + #[cfg(feature = "hal_v4")] + fn setAdditionalAttestationInfo( + &self, + info: &[keymint::KeyParameter::KeyParameter], + ) -> binder::Result<()> { + let _rsp: SetAdditionalAttestationInfoResponse = + self.execute(SetAdditionalAttestationInfoRequest { + info: info + .iter() + .filter_map(|p| p.try_innto().transpose()) + .collect::, _>>() + .map_err(failed_conversion)?, + })?; + Ok(()) + } +} + +/// Representation of an in-progress KeyMint operation on a `SerializedChannel`. +#[derive(Debug)] +struct Operation { + channel: Arc>, + op_handle: RwLock>, +} + +impl Drop for Operation { + fn drop(&mut self) { + // Ensure that the TA is kept up-to-date by calling `abort()`, but ignore the result. + let _ = self.abort(); + } +} + +impl ChannelHalService for Operation { + fn channel(&self) -> MutexGuard { + self.channel.lock().unwrap() + } + + /// Execute the given request as part of the operation. If the request fails, the operation is + /// invalidated (and any future requests for the operation will fail). + fn execute(&self, req: R) -> binder::Result + where + R: AsCborValue + Code, + S: AsCborValue + Code, + { + let result = super::channel_execute(self.channel().deref_mut(), req); + if result.is_err() { + // Any failed method on an operation terminates the operation. + self.invalidate(); + } + result + } +} + +impl binder::Interface for Operation {} + +impl Operation { + /// Create a new `Operation` wrapped in a proxy object. + fn new_as_binder( + channel: Arc>, + op_handle: i64, + ) -> binder::Strong { + let op = Self { channel, op_handle: RwLock::new(Some(op_handle)) }; + keymint::IKeyMintOperation::BnKeyMintOperation::new_binder( + op, + binder::BinderFeatures::default(), + ) + } +} + +impl Operation { + // Maximum size allowed for the operation data. + const MAX_DATA_SIZE: usize = T::MAX_SIZE - MAX_CBOR_OVERHEAD; + + /// Invalidate the operation. + fn invalidate(&self) { + *self.op_handle.write().unwrap() = None; + } + + /// Retrieve the operation handle, if not already failed. + fn validate_handle(&self) -> binder::Result { + self.op_handle.read().unwrap().ok_or_else(|| { + binder::Status::new_service_specific_error( + keymint::ErrorCode::ErrorCode::INVALID_OPERATION_HANDLE.0, + Some(&CString::new("Operation handle not valid").unwrap()), + ) + }) + } +} + +/// Implement the `IKeyMintOperation` interface for a [`Operation`]. Each method invocation is +/// serialized into a request message that is sent over the `Operation`'s channel, and a +/// corresponding response message is read. This response message is deserialized back into the +/// method's output value(s). +impl keymint::IKeyMintOperation::IKeyMintOperation + for Operation +{ + fn updateAad( + &self, + mut input: &[u8], + authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>, + timeStampToken: Option<&TimeStampToken>, + ) -> binder::Result<()> { + let req_template = UpdateAadRequest { + op_handle: self.validate_handle()?, + input: vec![], + auth_token: match authToken { + None => None, + Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?), + }, + timestamp_token: timeStampToken.map(|t| t.clone().innto()), + }; + while !input.is_empty() { + let mut req = req_template.clone(); + let batch_len = core::cmp::min(Self::MAX_DATA_SIZE, input.len()); + req.input = input[..batch_len].to_vec(); + input = &input[batch_len..]; + let _rsp: UpdateAadResponse = self.execute(req).inspect_err(|_| { + // Any failure invalidates the operation + self.invalidate(); + })?; + } + Ok(()) + } + fn update( + &self, + mut input: &[u8], + authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>, + timeStampToken: Option<&TimeStampToken>, + ) -> binder::Result> { + let req_template = UpdateRequest { + op_handle: self.validate_handle()?, + input: input.to_vec(), + auth_token: match authToken { + None => None, + Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?), + }, + timestamp_token: timeStampToken.map(|t| t.clone().innto()), + }; + let mut output = vec![]; + while !input.is_empty() { + let mut req = req_template.clone(); + let batch_len = core::cmp::min(Self::MAX_DATA_SIZE, input.len()); + req.input = input[..batch_len].to_vec(); + input = &input[batch_len..]; + let rsp: UpdateResponse = self.execute(req).inspect_err(|_| { + self.invalidate(); + })?; + output.extend_from_slice(&rsp.ret); + } + Ok(output) + } + fn finish( + &self, + input: Option<&[u8]>, + signature: Option<&[u8]>, + authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>, + timestampToken: Option<&TimeStampToken>, + confirmationToken: Option<&[u8]>, + ) -> binder::Result> { + let op_handle = self.validate_handle()?; + let auth_token = match authToken { + None => None, + Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?), + }; + let timestamp_token = timestampToken.map(|t| t.clone().innto()); + let confirmation_token = confirmationToken.map(|v| v.to_vec()); + + let mut output = vec![]; + let result: binder::Result = if let Some(mut input) = input { + let MAX_DATA_SIZE = Self::MAX_DATA_SIZE; + while input.len() > MAX_DATA_SIZE { + let req = UpdateRequest { + op_handle, + input: input[..MAX_DATA_SIZE].to_vec(), + auth_token: auth_token.clone(), + timestamp_token: timestamp_token.clone(), + }; + input = &input[MAX_DATA_SIZE..]; + let rsp: UpdateResponse = self.execute(req).inspect_err(|_| { + self.invalidate(); + })?; + output.extend_from_slice(&rsp.ret); + } + + self.execute(FinishRequest { + op_handle, + input: Some(input.to_vec()), + signature: signature.map(|v| v.to_vec()), + auth_token, + timestamp_token, + confirmation_token, + }) + } else { + self.execute(FinishRequest { + op_handle, + input: None, + signature: signature.map(|v| v.to_vec()), + auth_token, + timestamp_token, + confirmation_token, + }) + }; + // Finish always invalidates the operation. + self.invalidate(); + result.map(|rsp| { + output.extend_from_slice(&rsp.ret); + output + }) + } + fn abort(&self) -> binder::Result<()> { + let result: binder::Result = + self.execute(AbortRequest { op_handle: self.validate_handle()? }); + // Abort always invalidates the operation. + self.invalidate(); + let _ = result?; + Ok(()) + } +} diff --git a/libs/rust/hal/src/lib.rs b/libs/rust/hal/src/lib.rs new file mode 100644 index 0000000..ff777bd --- /dev/null +++ b/libs/rust/hal/src/lib.rs @@ -0,0 +1,318 @@ +// Copyright 2022, 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. + +//! Implementation of a HAL service for KeyMint. +//! +//! This implementation relies on a `SerializedChannel` abstraction for a communication channel to +//! the trusted application (TA). Incoming method invocations for the HAL service are converted +//! into corresponding request structures, which are then serialized (using CBOR) and send down the +//! channel. A serialized response is then read from the channel, which is deserialized into a +//! response structure. The contents of this response structure are then used to populate the +//! return values of the HAL service method. + +#![allow(non_snake_case)] + +use core::{convert::TryInto, fmt::Debug}; +use kmr_wire::{ + cbor, cbor_type_error, keymint::ErrorCode, keymint::NEXT_MESSAGE_SIGNAL_TRUE, AsCborValue, + CborError, Code, KeyMintOperation, +}; +use log::{error, info, warn}; +use std::{ + ffi::CString, + io::{Read, Write}, + ops::DerefMut, + sync::MutexGuard, +}; + +pub use binder; + +pub mod env; +pub mod hal; +pub mod keymint; +pub mod rpc; +pub mod secureclock; +pub mod sharedsecret; +#[cfg(test)] +mod tests; + +/// Emit a failure for a failed CBOR conversion. +#[inline] +pub fn failed_cbor(err: CborError) -> binder::Status { + binder::Status::new_service_specific_error( + ErrorCode::EncodingError as i32, + Some(&CString::new(format!("CBOR conversion failed: {:?}", err)).unwrap()), + ) +} + +/// Abstraction of a channel to a secure world TA implementation. +pub trait SerializedChannel: Debug + Send { + /// Maximum supported size for the channel in bytes. + const MAX_SIZE: usize; + + /// Accepts serialized request messages and returns serialized return values + /// (or an error if communication via the channel is lost). + fn execute(&mut self, serialized_req: &[u8]) -> binder::Result>; +} + +/// A helper method to be used in the [`execute`] method above, in order to handle +/// responses received from the TA, especially those which are larger than the capacity of the +/// channel between the HAL and the TA. +/// This inspects the message, checks the first byte to see if the response arrives in multiple +/// messages. A boolean indicating whether or not to wait for the next message and the +/// response content (with the first byte stripped off) are returned to +/// the HAL service . Implementation of this method must be in sync with its counterpart +/// in the `kmr-ta` crate. +pub fn extract_rsp(rsp: &[u8]) -> binder::Result<(bool, &[u8])> { + if rsp.len() < 2 { + return Err(binder::Status::new_exception( + binder::ExceptionCode::ILLEGAL_ARGUMENT, + Some(&CString::new("message is too small to extract the response data").unwrap()), + )); + } + Ok((rsp[0] == NEXT_MESSAGE_SIGNAL_TRUE, &rsp[1..])) +} + +/// Write a message to a stream-oriented [`Write`] item, with length framing. +pub fn write_msg(w: &mut W, data: &[u8]) -> binder::Result<()> { + // The underlying `Write` item does not guarantee delivery of complete messages. + // Make this possible by adding framing in the form of a big-endian `u32` holding + // the message length. + let data_len: u32 = data.len().try_into().map_err(|_e| { + binder::Status::new_exception( + binder::ExceptionCode::BAD_PARCELABLE, + Some(&CString::new("encoded request message too large").unwrap()), + ) + })?; + let data_len_data = data_len.to_be_bytes(); + w.write_all(&data_len_data[..]).map_err(|e| { + error!("Failed to write length to stream: {}", e); + binder::Status::new_exception( + binder::ExceptionCode::BAD_PARCELABLE, + Some(&CString::new("failed to write framing length").unwrap()), + ) + })?; + w.write_all(data).map_err(|e| { + error!("Failed to write data to stream: {}", e); + binder::Status::new_exception( + binder::ExceptionCode::BAD_PARCELABLE, + Some(&CString::new("failed to write data").unwrap()), + ) + })?; + Ok(()) +} + +/// Read a message from a stream-oriented [`Read`] item, with length framing. +pub fn read_msg(r: &mut R) -> binder::Result> { + // The data read from the `Read` item has a 4-byte big-endian length prefix. + let mut len_data = [0u8; 4]; + r.read_exact(&mut len_data).map_err(|e| { + error!("Failed to read length from stream: {}", e); + binder::Status::new_exception(binder::ExceptionCode::TRANSACTION_FAILED, None) + })?; + let len = u32::from_be_bytes(len_data); + let mut data = vec![0; len as usize]; + r.read_exact(&mut data).map_err(|e| { + error!("Failed to read data from stream: {}", e); + binder::Status::new_exception(binder::ExceptionCode::TRANSACTION_FAILED, None) + })?; + Ok(data) +} + +/// Message-oriented wrapper around a pair of stream-oriented channels. This allows a pair of +/// uni-directional channels that don't necessarily preserve message boundaries to appear as a +/// single bi-directional channel that does preserve message boundaries. +#[derive(Debug)] +pub struct MessageChannel { + r: R, + w: W, +} + +impl SerializedChannel for MessageChannel { + const MAX_SIZE: usize = 4096; + + fn execute(&mut self, serialized_req: &[u8]) -> binder::Result> { + write_msg(&mut self.w, serialized_req)?; + read_msg(&mut self.r) + } +} + +/// Execute an operation by serializing and sending a request structure down a channel, and +/// deserializing and returning the response. +/// +/// This implementation relies on the internal serialization format for `PerformOpReq` and +/// `PerformOpRsp` to allow direct use of the specific request/response types. +fn channel_execute(channel: &mut T, req: R) -> binder::Result +where + T: SerializedChannel, + R: AsCborValue + Code, + S: AsCborValue + Code, +{ + // Manually build an array that includes the opcode and the encoded request and encode it. + // This is equivalent to `PerformOpReq::to_vec()`. + let req_arr = cbor::value::Value::Array(vec![ + ::CODE.to_cbor_value().map_err(failed_cbor)?, + req.to_cbor_value().map_err(failed_cbor)?, + ]); + let mut req_data = Vec::new(); + cbor::ser::into_writer(&req_arr, &mut req_data).map_err(|e| { + binder::Status::new_service_specific_error( + ErrorCode::EncodingError as i32, + Some( + &CString::new(format!("failed to write CBOR request to buffer: {:?}", e)).unwrap(), + ), + ) + })?; + + if req_data.len() > T::MAX_SIZE { + error!( + "HAL operation {:?} encodes bigger {} than max size {}", + ::CODE, + req_data.len(), + T::MAX_SIZE + ); + return Err(binder::Status::new_service_specific_error( + ErrorCode::InvalidInputLength as i32, + Some(&CString::new("encoded request message too large").unwrap()), + )); + } + + // Send in request bytes, get back response bytes. + let rsp_data = channel.execute(&req_data)?; + + // Convert the raw response data to an array of [error code, opt_response]. + let rsp_value = kmr_wire::read_to_value(&rsp_data).map_err(failed_cbor)?; + let mut rsp_array = match rsp_value { + cbor::value::Value::Array(a) if a.len() == 2 => a, + _ => { + error!("HAL: failed to parse response data 2-array!"); + return cbor_type_error(&rsp_value, "arr of len 2").map_err(failed_cbor); + } + }; + let opt_response = rsp_array.remove(1); + let error_code = ::from_cbor_value(rsp_array.remove(0)).map_err(failed_cbor)?; + // The error code is in a numbering space that depends on the specific HAL being + // invoked (IRemotelyProvisionedComponent vs. the rest). However, the OK value is + // the same in all spaces. + if error_code != ErrorCode::Ok as i32 { + warn!("HAL: command {:?} failed: {:?}", ::CODE, error_code); + return Err(binder::Status::new_service_specific_error(error_code, None)); + } + + // The optional response should be an array of exactly 1 element (because the 0-element case + // corresponds to a non-OK error code, which has just been dealt with). + let rsp = match opt_response { + cbor::value::Value::Array(mut a) if a.len() == 1 => a.remove(0), + _ => { + error!("HAL: failed to parse response data structure!"); + return cbor_type_error(&opt_response, "arr of len 1").map_err(failed_cbor); + } + }; + + // The response is expected to be an array of 2 elements: a op_type code and an encoded response + // structure. The op_type code indicates the type of response structure, which should be what + // we expect. + let mut inner_rsp_array = match rsp { + cbor::value::Value::Array(a) if a.len() == 2 => a, + _ => { + error!("HAL: failed to parse inner response data structure!"); + return cbor_type_error(&rsp, "arr of len 2").map_err(failed_cbor); + } + }; + let inner_rsp = inner_rsp_array.remove(1); + let op_type = + ::from_cbor_value(inner_rsp_array.remove(0)).map_err(failed_cbor)?; + if op_type != ::CODE { + error!("HAL: inner response data for unexpected opcode {:?}!", op_type); + return Err(failed_cbor(CborError::UnexpectedItem("wrong ret code", "rsp ret code"))); + } + + ::from_cbor_value(inner_rsp).map_err(failed_cbor) +} + +/// Abstraction of a HAL service that uses an underlying [`SerializedChannel`] to communicate with +/// an associated TA. +trait ChannelHalService { + /// Return the underlying channel. + fn channel(&self) -> MutexGuard; + + /// Execute the given request, by serializing it and sending it down the internal channel. Then + /// read and deserialize the response. + fn execute(&self, req: R) -> binder::Result + where + R: AsCborValue + Code, + S: AsCborValue + Code, + { + channel_execute(self.channel().deref_mut(), req) + } +} + +/// Let the TA know information about the userspace environment. +pub fn send_hal_info(channel: &mut T) -> binder::Result<()> { + let req = env::populate_hal_info().map_err(|e| { + binder::Status::new_exception( + binder::ExceptionCode::BAD_PARCELABLE, + Some(&CString::new(format!("failed to determine HAL environment: {}", e)).unwrap()), + ) + })?; + info!("HAL->TA: environment info is {:?}", req); + let _rsp: kmr_wire::SetHalInfoResponse = channel_execute(channel, req)?; + + let aidl_version = if cfg!(feature = "hal_v4") { + 400 + } else if cfg!(feature = "hal_v3") { + 300 + } else if cfg!(feature = "hal_v2") { + 200 + } else { + 100 + }; + let req = kmr_wire::SetHalVersionRequest { aidl_version }; + info!("HAL->TA: setting KeyMint HAL version to {}", aidl_version); + let result: binder::Result = channel_execute(channel, req); + if let Err(e) = result { + // The SetHalVersionRequest message was added later; an earlier TA may not recognize it. + warn!("Setting KeyMint HAL version failed: {:?}", e); + } + Ok(()) +} + +/// Let the TA know information about the boot environment. +pub fn send_boot_info( + channel: &mut T, + req: kmr_wire::SetBootInfoRequest, +) -> binder::Result<()> { + info!("boot->TA: boot info is {:?}", req); + let _rsp: kmr_wire::SetBootInfoResponse = channel_execute(channel, req)?; + Ok(()) +} + +/// Provision the TA with attestation ID information. +pub fn send_attest_ids( + channel: &mut T, + ids: kmr_wire::AttestationIdInfo, +) -> binder::Result<()> { + let req = kmr_wire::SetAttestationIdsRequest { ids }; + info!("provision->attestation IDs are {:?}", req); + let _rsp: kmr_wire::SetAttestationIdsResponse = channel_execute(channel, req)?; + Ok(()) +} + +/// Let the TA know that early boot has ended +pub fn early_boot_ended(channel: &mut T) -> binder::Result<()> { + info!("boot->TA: early boot ended"); + let req = kmr_wire::EarlyBootEndedRequest {}; + let _rsp: kmr_wire::EarlyBootEndedResponse = channel_execute(channel, req)?; + Ok(()) +} diff --git a/libs/rust/hal/src/rpc.rs b/libs/rust/hal/src/rpc.rs new file mode 100644 index 0000000..1f9eeae --- /dev/null +++ b/libs/rust/hal/src/rpc.rs @@ -0,0 +1,103 @@ +// Copyright 2022, 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. + +//! RemotelyProvisionedComponent HAL device implementation. + +use super::{ChannelHalService, SerializedChannel}; +use crate::binder; +use crate::hal::{rkp, Innto}; +use kmr_wire::*; +use std::sync::{Arc, Mutex, MutexGuard}; + +/// `IRemotelyProvisionedComponent` implementation which converts all method invocations to +/// serialized requests that are sent down the associated channel. +pub struct Device { + channel: Arc>, +} + +impl Device { + /// Construct a new instance that uses the provided channel. + pub fn new(channel: Arc>) -> Self { + Self { channel } + } + + /// Create a new instance wrapped in a proxy object. + pub fn new_as_binder( + channel: Arc>, + ) -> binder::Strong { + rkp::IRemotelyProvisionedComponent::BnRemotelyProvisionedComponent::new_binder( + Self::new(channel), + binder::BinderFeatures::default(), + ) + } +} + +impl ChannelHalService for Device { + fn channel(&self) -> MutexGuard { + self.channel.lock().unwrap() + } +} + +impl binder::Interface for Device {} + +impl rkp::IRemotelyProvisionedComponent::IRemotelyProvisionedComponent + for Device +{ + fn getHardwareInfo(&self) -> binder::Result { + let rsp: GetRpcHardwareInfoResponse = self.execute(GetRpcHardwareInfoRequest {})?; + Ok(rsp.ret.innto()) + } + fn generateEcdsaP256KeyPair( + &self, + testMode: bool, + macedPublicKey: &mut rkp::MacedPublicKey::MacedPublicKey, + ) -> binder::Result> { + let rsp: GenerateEcdsaP256KeyPairResponse = + self.execute(GenerateEcdsaP256KeyPairRequest { test_mode: testMode })?; + *macedPublicKey = rsp.maced_public_key.innto(); + Ok(rsp.ret) + } + fn generateCertificateRequest( + &self, + testMode: bool, + keysToSign: &[rkp::MacedPublicKey::MacedPublicKey], + endpointEncryptionCertChain: &[u8], + challenge: &[u8], + deviceInfo: &mut rkp::DeviceInfo::DeviceInfo, + protectedData: &mut rkp::ProtectedData::ProtectedData, + ) -> binder::Result> { + let rsp: GenerateCertificateRequestResponse = + self.execute(GenerateCertificateRequestRequest { + test_mode: testMode, + keys_to_sign: keysToSign.iter().map(|k| k.innto()).collect(), + endpoint_encryption_cert_chain: endpointEncryptionCertChain.to_vec(), + challenge: challenge.to_vec(), + })?; + *deviceInfo = rsp.device_info.innto(); + *protectedData = rsp.protected_data.innto(); + Ok(rsp.ret) + } + fn generateCertificateRequestV2( + &self, + keysToSign: &[rkp::MacedPublicKey::MacedPublicKey], + challenge: &[u8], + ) -> binder::Result> { + let rsp: GenerateCertificateRequestV2Response = + self.execute(GenerateCertificateRequestV2Request { + keys_to_sign: keysToSign.iter().map(|k| k.innto()).collect(), + challenge: challenge.to_vec(), + })?; + Ok(rsp.ret) + } +} diff --git a/libs/rust/hal/src/secureclock.rs b/libs/rust/hal/src/secureclock.rs new file mode 100644 index 0000000..f4a502e --- /dev/null +++ b/libs/rust/hal/src/secureclock.rs @@ -0,0 +1,58 @@ +// Copyright 2022, 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. + +//! SecureClock HAL device implementation. + +use super::{ChannelHalService, SerializedChannel}; +use crate::binder; +use crate::hal::secureclock::{ISecureClock, TimeStampToken::TimeStampToken}; +use crate::hal::Innto; +use kmr_wire::*; +use std::sync::{Arc, Mutex, MutexGuard}; + +/// `ISecureClock` implementation which converts all method invocations to serialized requests that +/// are sent down the associated channel. +pub struct Device { + channel: Arc>, +} + +impl binder::Interface for Device {} + +impl Device { + /// Construct a new instance that uses the provided channel. + pub fn new(channel: Arc>) -> Self { + Self { channel } + } + /// Create a new instance wrapped in a proxy object. + pub fn new_as_binder(channel: Arc>) -> binder::Strong { + ISecureClock::BnSecureClock::new_binder( + Self::new(channel), + binder::BinderFeatures::default(), + ) + } +} + +impl ChannelHalService for Device { + fn channel(&self) -> MutexGuard { + self.channel.lock().unwrap() + } +} + +impl ISecureClock::ISecureClock for Device { + fn generateTimeStamp(&self, challenge: i64) -> binder::Result { + let rsp: GenerateTimeStampResponse = + self.execute(GenerateTimeStampRequest { challenge })?; + Ok(rsp.ret.innto()) + } +} diff --git a/libs/rust/hal/src/sharedsecret.rs b/libs/rust/hal/src/sharedsecret.rs new file mode 100644 index 0000000..aeec892 --- /dev/null +++ b/libs/rust/hal/src/sharedsecret.rs @@ -0,0 +1,67 @@ +// Copyright 2022, 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. + +//! SharedSecret HAL device implementation. + +use crate::binder; +use crate::hal::{ + sharedsecret::{ISharedSecret, SharedSecretParameters::SharedSecretParameters}, + Innto, +}; +use crate::{ChannelHalService, SerializedChannel}; +use kmr_wire::*; +use std::sync::{Arc, Mutex, MutexGuard}; + +/// `ISharedSecret` implementation which converts all method invocations to serialized requests that +/// are sent down the associated channel. +pub struct Device { + channel: Arc>, +} + +impl binder::Interface for Device {} + +impl Device { + /// Construct a new instance that uses the provided channel. + pub fn new(channel: Arc>) -> Self { + Self { channel } + } + /// Create a new instance wrapped in a proxy object. + pub fn new_as_binder( + channel: Arc>, + ) -> binder::Strong { + ISharedSecret::BnSharedSecret::new_binder( + Self::new(channel), + binder::BinderFeatures::default(), + ) + } +} + +impl ChannelHalService for Device { + fn channel(&self) -> MutexGuard { + self.channel.lock().unwrap() + } +} + +impl ISharedSecret::ISharedSecret for Device { + fn getSharedSecretParameters(&self) -> binder::Result { + let rsp: GetSharedSecretParametersResponse = + self.execute(GetSharedSecretParametersRequest {})?; + Ok(rsp.ret.innto()) + } + fn computeSharedSecret(&self, params: &[SharedSecretParameters]) -> binder::Result> { + let rsp: ComputeSharedSecretResponse = + self.execute(ComputeSharedSecretRequest { params: params.to_vec().innto() })?; + Ok(rsp.ret) + } +} diff --git a/libs/rust/hal/src/tests.rs b/libs/rust/hal/src/tests.rs new file mode 100644 index 0000000..9985270 --- /dev/null +++ b/libs/rust/hal/src/tests.rs @@ -0,0 +1,178 @@ +// Copyright 2022, 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. + +use super::*; +use crate::{ + binder, + hal::keymint::{ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice}, + keymint::MAX_CBOR_OVERHEAD, +}; +use kmr_wire::{ + keymint::{ + HardwareAuthToken, HardwareAuthenticatorType, NEXT_MESSAGE_SIGNAL_FALSE, + NEXT_MESSAGE_SIGNAL_TRUE, + }, + secureclock::{TimeStampToken, Timestamp}, + FinishRequest, PerformOpReq, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Clone, Debug)] +struct TestChannel { + req: Arc>>, + rsp: Vec, +} + +impl TestChannel { + fn new(rsp: &str) -> Self { + Self { req: Arc::new(Mutex::new(vec![])), rsp: hex::decode(rsp).unwrap() } + } + fn req_data(&self) -> Vec { + self.req.lock().unwrap().clone() + } +} + +impl SerializedChannel for TestChannel { + const MAX_SIZE: usize = 4096; + fn execute(&mut self, serialized_req: &[u8]) -> binder::Result> { + *self.req.lock().unwrap() = serialized_req.to_vec(); + Ok(self.rsp.clone()) + } +} + +#[test] +fn test_method_roundtrip() { + let channel = TestChannel::new(concat!( + "82", // 2-arr (PerformOpResponse) + "00", // int (PerformOpResponse.error_code == ErrorCode::Ok) + "81", // 1-arr (PerformOpResponse.rsp) + "82", // 2-arr (PerformOpResponse.rsp.0 : PerformOpRsp) + "13", // 0x13 = KeyMintOperation::DEVICE_GENERATE_KEY + "81", // 1-arr (GenerateKeyResponse) + "83", // 3-arr (ret: KeyCreationResult) + "41", "01", // 1-bstr (KeyCreationResult.keyBlob) + "80", // 0-arr (KeyCreationResult.keyCharacteristics) + "80", // 0-arr (KeyCreationResult.certificateChain) + )); + let imp = keymint::Device::new(Arc::new(Mutex::new(channel.clone()))); + + let result = imp.generateKey(&[], None).unwrap(); + + let want_req = concat!( + "82", // 2-arr (PerformOpReq) + "13", // 0x13 = DEVICE_GENERATE_KEY + "82", // 1-arr (GenerateKeyRequest) + "80", // 0-arr (* KeyParameter) + "80", // 0-arr (? AttestationKey) + ); + assert_eq!(channel.req_data(), hex::decode(want_req).unwrap()); + + assert_eq!(result.keyBlob, vec![0x01]); + assert!(result.keyCharacteristics.is_empty()); + assert!(result.certificateChain.is_empty()); +} + +#[test] +fn test_method_err_roundtrip() { + let channel = TestChannel::new(concat!( + "82", // 2-arr (PerformOpResponse) + "21", // (PerformOpResponse.error_code = ErrorCode::UNSUPPORTED_PURPOSE) + "80", // 0-arr (PerformOpResponse.rsp) + )); + let imp = keymint::Device::new(Arc::new(Mutex::new(channel.clone()))); + + let result = imp.generateKey(&[], None); + + let want_req = concat!( + "82", // 2-arr (PerformOpReq) + "13", // 0x13 = DEVICE_GENERATE_KEY + "82", // 1-arr (GenerateKeyRequest) + "80", // 0-arr (* KeyParameter) + "80", // 0-arr (? AttestationKey) + ); + assert_eq!(channel.req_data(), hex::decode(want_req).unwrap()); + + assert!(result.is_err()); + let status = result.unwrap_err(); + assert_eq!(status.exception_code(), binder::ExceptionCode::SERVICE_SPECIFIC); + assert_eq!(status.service_specific_error(), ErrorCode::UNSUPPORTED_PURPOSE.0); +} + +#[test] +fn test_overhead_size() { + let largest_op_req = PerformOpReq::OperationFinish(FinishRequest { + op_handle: 0x7fffffff, + input: Some(Vec::new()), + signature: Some(vec![0; 132]), + auth_token: Some(HardwareAuthToken { + challenge: 0x7fffffff, + user_id: 0x7fffffff, + authenticator_id: 0x7fffffff, + authenticator_type: HardwareAuthenticatorType::Password, + timestamp: Timestamp { milliseconds: 0x7fffffff }, + mac: vec![0; 32], + }), + timestamp_token: Some(TimeStampToken { + challenge: 0x7fffffff, + timestamp: Timestamp { milliseconds: 0x7fffffff }, + mac: vec![0; 32], + }), + confirmation_token: Some(vec![0; 32]), + }); + let data = largest_op_req.into_vec().unwrap(); + assert!( + data.len() < MAX_CBOR_OVERHEAD, + "expect largest serialized empty message (size {}) to be smaller than {}", + data.len(), + MAX_CBOR_OVERHEAD + ); +} + +#[test] +fn test_extract_rsp_true_marker() { + let msg_content = vec![0x82, 0x21, 0x80]; + // test true marker and message content + let mut resp = vec![NEXT_MESSAGE_SIGNAL_TRUE]; + resp.extend_from_slice(&msg_content); + assert_eq!(Ok((true, msg_content.as_slice())), extract_rsp(&resp)); +} + +#[test] +fn test_extract_rsp_false_marker() { + let msg_content = vec![0x82, 0x21, 0x80]; + // test false signal and message content + let mut resp = vec![NEXT_MESSAGE_SIGNAL_FALSE]; + resp.extend_from_slice(&msg_content); + assert_eq!(Ok((false, msg_content.as_slice())), extract_rsp(&resp)); +} + +#[test] +fn test_extract_rsp_empty_input() { + // test invalid (empty) input + let resp3 = vec![]; + let result = extract_rsp(&resp3); + assert!(result.is_err()); + let status = result.unwrap_err(); + assert_eq!(status.exception_code(), binder::ExceptionCode::ILLEGAL_ARGUMENT); +} + +#[test] +fn test_extract_rsp_single_byte_input() { + // test invalid (single byte) input + let resp4 = vec![NEXT_MESSAGE_SIGNAL_FALSE]; + let result = extract_rsp(&resp4); + assert!(result.is_err()); + let status = result.unwrap_err(); + assert_eq!(status.exception_code(), binder::ExceptionCode::ILLEGAL_ARGUMENT); +} diff --git a/libs/rust/proto/storage.proto b/libs/rust/proto/storage.proto new file mode 100644 index 0000000..7967454 --- /dev/null +++ b/libs/rust/proto/storage.proto @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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. + */ + +syntax = "proto3"; + +package storage; + +message SecureDeletionData { + // Counter used to generate new slot IDs. Increment it to allocate a slot ID. Ideally, we + // should reuse freed slot IDs. A simple counter should suffice here though. + uint32 last_free_slot = 1; + bytes factory_secret = 2; + map secure_deletion_secrets = 3; +} diff --git a/libs/rust/scripts/cddl-gen b/libs/rust/scripts/cddl-gen new file mode 100755 index 0000000..659f666 --- /dev/null +++ b/libs/rust/scripts/cddl-gen @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e + +# Regenerate CDDL files +cargo run --bin cddl-dump > common/generated.cddl +cargo run --bin keyblob-cddl-dump > common/src/keyblob/keyblob.cddl diff --git a/libs/rust/src/.gitignore b/libs/rust/src/.gitignore new file mode 100644 index 0000000..1337e90 --- /dev/null +++ b/libs/rust/src/.gitignore @@ -0,0 +1 @@ +/proto \ No newline at end of file diff --git a/libs/rust/src/attest.rs b/libs/rust/src/attest.rs new file mode 100644 index 0000000..1ce2066 --- /dev/null +++ b/libs/rust/src/attest.rs @@ -0,0 +1,425 @@ +// +// Copyright (C) 2022 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. + +//! Attestation keys and certificates. +//! +//! Hard-coded keys and certs copied from system/keymaster/context/soft_attestation_cert.cpp + +use kmr_common::{ + crypto::ec, crypto::rsa, crypto::CurveType, crypto::KeyMaterial, wire::keymint, + wire::keymint::EcCurve, Error, +}; +use kmr_ta::device::{RetrieveCertSigningInfo, SigningAlgorithm, SigningKeyType}; + +/// RSA attestation private key in PKCS#1 format. +/// +/// Decoded contents (using [der2ascii](https://github.com/google/der-ascii)): +/// +/// ``` +/// SEQUENCE { +/// INTEGER { 0 } +/// INTEGER { `00c08323dc56881bb8302069f5b08561c6eebe7f05e2f5a842048abe8b47be76feaef25cf29b2afa3200141601429989a15fcfc6815eb363583c2fd2f20be4983283dd814b16d7e185417ae54abc296a3a6db5c004083b68c556c1f02339916419864d50b74d40aeca484c77356c895a0c275abfac499d5d7d2362f29c5e02e871` } +/// INTEGER { 65537 } +/// INTEGER { `00be860b0b99a802a6fb1a59438a7bb715065b09a36dc6e9cacc6bf3c02c34d7d79e94c6606428d88c7b7f6577c1cdea64074abe8e7286df1f0811dc9728260868de95d32efc96b6d084ff271a5f60defcc703e7a38e6e29ba9a3c5fc2c28076b6a896af1d34d78828ce9bddb1f34f9c9404430781298e201316725bbdbc993a41` } +/// INTEGER { `00e1c6d927646c0916ec36826d594983740c21f1b074c4a1a59867c669795c85d3dc464c5b929e94bfb34e0dcc5014b10f13341ab7fdd5f60414d2a326cad41cc5` } +/// INTEGER { `00da485997785cd5630fb0fd8c5254f98e538e18983aae9e6b7e6a5a7b5d343755b9218ebd40320d28387d789f76fa218bcc2d8b68a5f6418fbbeca5179ab3afbd` } +/// INTEGER { `50fefc32649559616ed6534e154509329d93a3d810dbe5bdb982292cf78bd8badb8020ae8d57f4b71d05386ffe9e9db271ca3477a34999db76f8e5ece9c0d49d` } +/// INTEGER { `15b74cf27cceff8bb36bf04d9d8346b09a2f70d2f4439b0f26ac7e03f7e9d1f77d4b915fd29b2823f03acb5d5200e0857ff2a803e93eee96d6235ce95442bc21` } +/// INTEGER { `0090a745da8970b2cd649660324228c5f82856ffd665ba9a85c8d60f1b8bee717ecd2c72eae01dad86ba7654d4cf45adb5f1f2b31d9f8122cfa5f1a5570f9b2d25` } +/// } +/// ``` +const RSA_ATTEST_KEY: &str = concat!( + "3082025d02010002818100c08323dc56881bb8302069f5b08561c6eebe7f05e2", + "f5a842048abe8b47be76feaef25cf29b2afa3200141601429989a15fcfc6815e", + "b363583c2fd2f20be4983283dd814b16d7e185417ae54abc296a3a6db5c00408", + "3b68c556c1f02339916419864d50b74d40aeca484c77356c895a0c275abfac49", + "9d5d7d2362f29c5e02e871020301000102818100be860b0b99a802a6fb1a5943", + "8a7bb715065b09a36dc6e9cacc6bf3c02c34d7d79e94c6606428d88c7b7f6577", + "c1cdea64074abe8e7286df1f0811dc9728260868de95d32efc96b6d084ff271a", + "5f60defcc703e7a38e6e29ba9a3c5fc2c28076b6a896af1d34d78828ce9bddb1", + "f34f9c9404430781298e201316725bbdbc993a41024100e1c6d927646c0916ec", + "36826d594983740c21f1b074c4a1a59867c669795c85d3dc464c5b929e94bfb3", + "4e0dcc5014b10f13341ab7fdd5f60414d2a326cad41cc5024100da485997785c", + "d5630fb0fd8c5254f98e538e18983aae9e6b7e6a5a7b5d343755b9218ebd4032", + "0d28387d789f76fa218bcc2d8b68a5f6418fbbeca5179ab3afbd024050fefc32", + "649559616ed6534e154509329d93a3d810dbe5bdb982292cf78bd8badb8020ae", + "8d57f4b71d05386ffe9e9db271ca3477a34999db76f8e5ece9c0d49d024015b7", + "4cf27cceff8bb36bf04d9d8346b09a2f70d2f4439b0f26ac7e03f7e9d1f77d4b", + "915fd29b2823f03acb5d5200e0857ff2a803e93eee96d6235ce95442bc210241", + "0090a745da8970b2cd649660324228c5f82856ffd665ba9a85c8d60f1b8bee71", + "7ecd2c72eae01dad86ba7654d4cf45adb5f1f2b31d9f8122cfa5f1a5570f9b2d", + "25", +); + +/// Attestation certificate corresponding to [`RSA_ATTEST_KEY`], signed by the key in +/// [`RSA_ATTEST_ROOT_CERT`]. +/// +/// Decoded contents: +/// +/// ``` +/// Certificate: +/// Data: +/// Version: 3 (0x2) +/// Serial Number: 4096 (0x1000) +/// Signature Algorithm: SHA256-RSA +/// Issuer: C=US, O=Google, Inc., OU=Android, L=Mountain View, ST=California +/// Validity: +/// Not Before: 2016-01-04 12:40:53 +0000 UTC +/// Not After : 2035-12-30 12:40:53 +0000 UTC +/// Subject: C=US, O=Google, Inc., OU=Android, ST=California, CN=Android Software Attestation Key +/// Subject Public Key Info: +/// Public Key Algorithm: rsaEncryption +/// Public Key: (1024 bit) +/// Modulus: +/// c0:83:23:dc:56:88:1b:b8:30:20:69:f5:b0:85:61: +/// c6:ee:be:7f:05:e2:f5:a8:42:04:8a:be:8b:47:be: +/// 76:fe:ae:f2:5c:f2:9b:2a:fa:32:00:14:16:01:42: +/// 99:89:a1:5f:cf:c6:81:5e:b3:63:58:3c:2f:d2:f2: +/// 0b:e4:98:32:83:dd:81:4b:16:d7:e1:85:41:7a:e5: +/// 4a:bc:29:6a:3a:6d:b5:c0:04:08:3b:68:c5:56:c1: +/// f0:23:39:91:64:19:86:4d:50:b7:4d:40:ae:ca:48: +/// 4c:77:35:6c:89:5a:0c:27:5a:bf:ac:49:9d:5d:7d: +/// 23:62:f2:9c:5e:02:e8:71: +/// Exponent: 65537 (0x10001) +/// X509v3 extensions: +/// X509v3 Authority Key Identifier: +/// keyid:29faf1accc4dd24c96402775b6b0e932e507fe2e +/// X509v3 Subject Key Identifier: +/// keyid:d40c101bf8cd63b9f73952b50e135ca6d7999386 +/// X509v3 Key Usage: critical +/// Digital Signature, Certificate Signing +/// X509v3 Basic Constraints: critical +/// CA:true, pathlen:0 +/// Signature Algorithm: SHA256-RSA +/// 9e:2d:48:5f:8c:67:33:dc:1a:85:ad:99:d7:50:23:ea:14:ec: +/// 43:b0:e1:9d:ea:c2:23:46:1e:72:b5:19:dc:60:22:e4:a5:68: +/// 31:6c:0b:55:c4:e6:9c:a2:2d:9f:3a:4f:93:6b:31:8b:16:78: +/// 16:0d:88:cb:d9:8b:cc:80:9d:84:f0:c2:27:e3:6b:38:f1:fd: +/// d1:e7:17:72:31:59:35:7d:96:f3:c5:7f:ab:9d:8f:96:61:26: +/// 4f:b2:be:81:bb:0d:49:04:22:8a:ce:9f:f7:f5:42:2e:25:44: +/// fa:21:07:12:5a:83:b5:55:ad:18:82:f8:40:14:9b:9c:20:63: +/// 04:7f: +/// ``` +const RSA_ATTEST_CERT: &str = concat!( + "308202b63082021fa00302010202021000300d06092a864886f70d01010b0500", + "3063310b30090603550406130255533113301106035504080c0a43616c69666f", + "726e69613116301406035504070c0d4d6f756e7461696e205669657731153013", + "060355040a0c0c476f6f676c652c20496e632e3110300e060355040b0c07416e", + "64726f6964301e170d3136303130343132343035335a170d3335313233303132", + "343035335a3076310b30090603550406130255533113301106035504080c0a43", + "616c69666f726e696131153013060355040a0c0c476f6f676c652c20496e632e", + "3110300e060355040b0c07416e64726f69643129302706035504030c20416e64", + "726f696420536f667477617265204174746573746174696f6e204b657930819f", + "300d06092a864886f70d010101050003818d0030818902818100c08323dc5688", + "1bb8302069f5b08561c6eebe7f05e2f5a842048abe8b47be76feaef25cf29b2a", + "fa3200141601429989a15fcfc6815eb363583c2fd2f20be4983283dd814b16d7", + "e185417ae54abc296a3a6db5c004083b68c556c1f02339916419864d50b74d40", + "aeca484c77356c895a0c275abfac499d5d7d2362f29c5e02e8710203010001a3", + "663064301d0603551d0e04160414d40c101bf8cd63b9f73952b50e135ca6d799", + "9386301f0603551d2304183016801429faf1accc4dd24c96402775b6b0e932e5", + "07fe2e30120603551d130101ff040830060101ff020100300e0603551d0f0101", + "ff040403020284300d06092a864886f70d01010b0500038181009e2d485f8c67", + "33dc1a85ad99d75023ea14ec43b0e19deac223461e72b519dc6022e4a568316c", + "0b55c4e69ca22d9f3a4f936b318b1678160d88cbd98bcc809d84f0c227e36b38", + "f1fdd1e717723159357d96f3c57fab9d8f9661264fb2be81bb0d4904228ace9f", + "f7f5422e2544fa2107125a83b555ad1882f840149b9c2063047f", +); + +/// Attestation self-signed root certificate holding the key that signed [`RSA_ATTEST_CERT`]. +/// +/// Decoded contents: +/// +/// ``` +/// Certificate: +/// Data: +/// Version: 3 (0x2) +/// Serial Number: 18416584322103887884 (0xff94d9dd9f07c80c) +/// Signature Algorithm: SHA256-RSA +/// Issuer: C=US, O=Google, Inc., OU=Android, L=Mountain View, ST=California +/// Validity: +/// Not Before: 2016-01-04 12:31:08 +0000 UTC +/// Not After : 2035-12-30 12:31:08 +0000 UTC +/// Subject: C=US, O=Google, Inc., OU=Android, L=Mountain View, ST=California +/// Subject Public Key Info: +/// Public Key Algorithm: rsaEncryption +/// Public Key: (1024 bit) +/// Modulus: +/// a2:6b:ad:eb:6e:2e:44:61:ef:d5:0e:82:e6:b7:94: +/// d1:75:23:1f:77:9b:63:91:63:ff:f7:aa:ff:0b:72: +/// 47:4e:c0:2c:43:ec:33:7c:d7:ac:ed:40:3e:8c:28: +/// a0:66:d5:f7:87:0b:33:97:de:0e:b8:4e:13:40:ab: +/// af:a5:27:bf:95:69:a0:31:db:06:52:65:f8:44:59: +/// 57:61:f0:bb:f2:17:4b:b7:41:80:64:c0:28:0e:8f: +/// 52:77:8e:db:d2:47:b6:45:e9:19:c8:e9:8b:c3:db: +/// c2:91:3f:d7:d7:50:c4:1d:35:66:f9:57:e4:97:96: +/// 0b:09:ac:ce:92:35:85:9b: +/// Exponent: 65537 (0x10001) +/// X509v3 extensions: +/// X509v3 Authority Key Identifier: +/// keyid:29faf1accc4dd24c96402775b6b0e932e507fe2e +/// X509v3 Subject Key Identifier: +/// keyid:29faf1accc4dd24c96402775b6b0e932e507fe2e +/// X509v3 Key Usage: critical +/// Digital Signature, Certificate Signing +/// X509v3 Basic Constraints: critical +/// CA:true +/// Signature Algorithm: SHA256-RSA +/// 4f:72:f3:36:59:8d:0e:c1:b9:74:5b:31:59:f6:f0:8d:25:49: +/// 30:9e:a3:1c:1c:29:d2:45:2d:20:b9:4d:5f:64:b4:e8:80:c7: +/// 78:7a:9c:39:de:a8:b3:f5:bf:2f:70:5f:47:10:5c:c5:e6:eb: +/// 4d:06:99:61:d2:ae:9a:07:ff:f7:7c:b8:ab:eb:9c:0f:24:07: +/// 5e:b1:7f:ba:79:71:fd:4d:5b:9e:df:14:a9:fe:df:ed:7c:c0: +/// 88:5d:f8:dd:9b:64:32:56:d5:35:9a:e2:13:f9:8f:ce:c1:7c: +/// dc:ef:a4:aa:b2:55:c3:83:a9:2e:fb:5c:f6:62:f5:27:52:17: +/// be:63: +/// ``` +const RSA_ATTEST_ROOT_CERT: &str = concat!( + "308202a730820210a003020102020900ff94d9dd9f07c80c300d06092a864886", + "f70d01010b05003063310b30090603550406130255533113301106035504080c", + "0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e2056", + "69657731153013060355040a0c0c476f6f676c652c20496e632e3110300e0603", + "55040b0c07416e64726f6964301e170d3136303130343132333130385a170d33", + "35313233303132333130385a3063310b30090603550406130255533113301106", + "035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e74", + "61696e205669657731153013060355040a0c0c476f6f676c652c20496e632e31", + "10300e060355040b0c07416e64726f696430819f300d06092a864886f70d0101", + "01050003818d0030818902818100a26badeb6e2e4461efd50e82e6b794d17523", + "1f779b639163fff7aaff0b72474ec02c43ec337cd7aced403e8c28a066d5f787", + "0b3397de0eb84e1340abafa527bf9569a031db065265f844595761f0bbf2174b", + "b7418064c0280e8f52778edbd247b645e919c8e98bc3dbc2913fd7d750c41d35", + "66f957e497960b09acce9235859b0203010001a3633061301d0603551d0e0416", + "041429faf1accc4dd24c96402775b6b0e932e507fe2e301f0603551d23041830", + "16801429faf1accc4dd24c96402775b6b0e932e507fe2e300f0603551d130101", + "ff040530030101ff300e0603551d0f0101ff040403020284300d06092a864886", + "f70d01010b0500038181004f72f336598d0ec1b9745b3159f6f08d2549309ea3", + "1c1c29d2452d20b94d5f64b4e880c7787a9c39dea8b3f5bf2f705f47105cc5e6", + "eb4d069961d2ae9a07fff77cb8abeb9c0f24075eb17fba7971fd4d5b9edf14a9", + "fedfed7cc0885df8dd9b643256d5359ae213f98fcec17cdcefa4aab255c383a9", + "2efb5cf662f5275217be63", +); + +/// EC attestation private key in `ECPrivateKey` format. +/// +/// Decoded contents (using [der2ascii](https://github.com/google/der-ascii)): +/// +/// ``` +/// SEQUENCE { +/// INTEGER { 1 } +/// OCTET_STRING { `21e086432a15198459cf363a50fc14c9daadf935f527c2dfd71e4d6dbc42e544` } +/// [0] { +/// # secp256r1 +/// OBJECT_IDENTIFIER { 1.2.840.10045.3.1.7 } +/// } +/// [1] { +/// BIT_STRING { `00` `04eb9e79f8426359accb2a914c8986cc70ad90669382a9732613feaccbf821274c2174974a2afea5b94d7f66d4e065106635bc53b7a0a3a671583edb3e11ae1014` } +/// } +/// } +/// ``` +const EC_ATTEST_KEY: &str = concat!( + "3077020101042021e086432a15198459cf363a50fc14c9daadf935f527c2dfd7", + "1e4d6dbc42e544a00a06082a8648ce3d030107a14403420004eb9e79f8426359", + "accb2a914c8986cc70ad90669382a9732613feaccbf821274c2174974a2afea5", + "b94d7f66d4e065106635bc53b7a0a3a671583edb3e11ae1014", +); + +/// Attestation certificate corresponding to [`EC_ATTEST_KEY`], signed by the key in +/// [`EC_ATTEST_ROOT_CERT`]. +/// +/// Decoded contents: +/// +/// ``` +/// Certificate: +/// Data: +/// Version: 3 (0x2) +/// Serial Number: 4097 (0x1001) +/// Signature Algorithm: ECDSA-SHA256 +/// Issuer: C=US, O=Google, Inc., OU=Android, L=Mountain View, ST=California, CN=Android Keystore Software Attestation Root +/// Validity: +/// Not Before: 2016-01-11 00:46:09 +0000 UTC +/// Not After : 2026-01-08 00:46:09 +0000 UTC +/// Subject: C=US, O=Google, Inc., OU=Android, ST=California, CN=Android Keystore Software Attestation Intermediate +/// Subject Public Key Info: +/// Public Key Algorithm: id-ecPublicKey +/// Public Key: (256 bit) +/// pub: +/// 04:eb:9e:79:f8:42:63:59:ac:cb:2a:91:4c:89:86: +/// cc:70:ad:90:66:93:82:a9:73:26:13:fe:ac:cb:f8: +/// 21:27:4c:21:74:97:4a:2a:fe:a5:b9:4d:7f:66:d4: +/// e0:65:10:66:35:bc:53:b7:a0:a3:a6:71:58:3e:db: +/// 3e:11:ae:10:14: +/// ASN1 OID: prime256v1 +/// X509v3 extensions: +/// X509v3 Authority Key Identifier: +/// keyid:c8ade9774c45c3a3cf0d1610e479433a215a30cf +/// X509v3 Subject Key Identifier: +/// keyid:3ffcacd61ab13a9e8120b8d5251cc565bb1e91a9 +/// X509v3 Key Usage: critical +/// Digital Signature, Certificate Signing +/// X509v3 Basic Constraints: critical +/// CA:true, pathlen:0 +/// Signature Algorithm: ECDSA-SHA256 +/// 30:45:02:20:4b:8a:9b:7b:ee:82:bc:c0:33:87:ae:2f:c0:89: +/// 98:b4:dd:c3:8d:ab:27:2a:45:9f:69:0c:c7:c3:92:d4:0f:8e: +/// 02:21:00:ee:da:01:5d:b6:f4:32:e9:d4:84:3b:62:4c:94:04: +/// ef:3a:7c:cc:bd:5e:fb:22:bb:e7:fe:b9:77:3f:59:3f:fb: +/// ``` +const EC_ATTEST_CERT: &str = concat!( + "308202783082021ea00302010202021001300a06082a8648ce3d040302308198", + "310b30090603550406130255533113301106035504080c0a43616c69666f726e", + "69613116301406035504070c0d4d6f756e7461696e2056696577311530130603", + "55040a0c0c476f6f676c652c20496e632e3110300e060355040b0c07416e6472", + "6f69643133303106035504030c2a416e64726f6964204b657973746f72652053", + "6f667477617265204174746573746174696f6e20526f6f74301e170d31363031", + "31313030343630395a170d3236303130383030343630395a308188310b300906", + "03550406130255533113301106035504080c0a43616c69666f726e6961311530", + "13060355040a0c0c476f6f676c652c20496e632e3110300e060355040b0c0741", + "6e64726f6964313b303906035504030c32416e64726f6964204b657973746f72", + "6520536f667477617265204174746573746174696f6e20496e7465726d656469", + "6174653059301306072a8648ce3d020106082a8648ce3d03010703420004eb9e", + "79f8426359accb2a914c8986cc70ad90669382a9732613feaccbf821274c2174", + "974a2afea5b94d7f66d4e065106635bc53b7a0a3a671583edb3e11ae1014a366", + "3064301d0603551d0e041604143ffcacd61ab13a9e8120b8d5251cc565bb1e91", + "a9301f0603551d23041830168014c8ade9774c45c3a3cf0d1610e479433a215a", + "30cf30120603551d130101ff040830060101ff020100300e0603551d0f0101ff", + "040403020284300a06082a8648ce3d040302034800304502204b8a9b7bee82bc", + "c03387ae2fc08998b4ddc38dab272a459f690cc7c392d40f8e022100eeda015d", + "b6f432e9d4843b624c9404ef3a7cccbd5efb22bbe7feb9773f593ffb", +); + +/// Attestation self-signed root certificate holding the key that signed [`EC_ATTEST_CERT`]. +/// +/// Decoded contents: +/// +/// ``` +/// Certificate: +/// Data: +/// Version: 3 (0x2) +/// Serial Number: 11674912229752527703 (0xa2059ed10e435b57) +/// Signature Algorithm: ECDSA-SHA256 +/// Issuer: C=US, O=Google, Inc., OU=Android, L=Mountain View, ST=California, CN=Android Keystore Software Attestation Root +/// Validity: +/// Not Before: 2016-01-11 00:43:50 +0000 UTC +/// Not After : 2036-01-06 00:43:50 +0000 UTC +/// Subject: C=US, O=Google, Inc., OU=Android, L=Mountain View, ST=California, CN=Android Keystore Software Attestation Root +/// Subject Public Key Info: +/// Public Key Algorithm: id-ecPublicKey +/// Public Key: (256 bit) +/// pub: +/// 04:ee:5d:5e:c7:e1:c0:db:6d:03:a6:7e:e6:b6:1b: +/// ec:4d:6a:5d:6a:68:2e:0f:ff:7f:49:0e:7d:77:1f: +/// 44:22:6d:bd:b1:af:fa:16:cb:c7:ad:c5:77:d2:56: +/// 9c:aa:b7:b0:2d:54:01:5d:3e:43:2b:2a:8e:d7:4e: +/// ec:48:75:41:a4: +/// ASN1 OID: prime256v1 +/// X509v3 extensions: +/// X509v3 Authority Key Identifier: +/// keyid:c8ade9774c45c3a3cf0d1610e479433a215a30cf +/// X509v3 Subject Key Identifier: +/// keyid:c8ade9774c45c3a3cf0d1610e479433a215a30cf +/// X509v3 Key Usage: critical +/// Digital Signature, Certificate Signing +/// X509v3 Basic Constraints: critical +/// CA:true +/// Signature Algorithm: ECDSA-SHA256 +/// 30:44:02:20:35:21:a3:ef:8b:34:46:1e:9c:d5:60:f3:1d:58: +/// 89:20:6a:dc:a3:65:41:f6:0d:9e:ce:8a:19:8c:66:48:60:7b: +/// 02:20:4d:0b:f3:51:d9:30:7c:7d:5b:da:35:34:1d:a8:47:1b: +/// 63:a5:85:65:3c:ad:4f:24:a7:e7:4d:af:41:7d:f1:bf: +/// ``` +const EC_ATTEST_ROOT_CERT: &str = concat!( + "3082028b30820232a003020102020900a2059ed10e435b57300a06082a8648ce", + "3d040302308198310b30090603550406130255533113301106035504080c0a43", + "616c69666f726e69613116301406035504070c0d4d6f756e7461696e20566965", + "7731153013060355040a0c0c476f6f676c652c20496e632e3110300e06035504", + "0b0c07416e64726f69643133303106035504030c2a416e64726f6964204b6579", + "73746f726520536f667477617265204174746573746174696f6e20526f6f7430", + "1e170d3136303131313030343335305a170d3336303130363030343335305a30", + "8198310b30090603550406130255533113301106035504080c0a43616c69666f", + "726e69613116301406035504070c0d4d6f756e7461696e205669657731153013", + "060355040a0c0c476f6f676c652c20496e632e3110300e060355040b0c07416e", + "64726f69643133303106035504030c2a416e64726f6964204b657973746f7265", + "20536f667477617265204174746573746174696f6e20526f6f74305930130607", + "2a8648ce3d020106082a8648ce3d03010703420004ee5d5ec7e1c0db6d03a67e", + "e6b61bec4d6a5d6a682e0fff7f490e7d771f44226dbdb1affa16cbc7adc577d2", + "569caab7b02d54015d3e432b2a8ed74eec487541a4a3633061301d0603551d0e", + "04160414c8ade9774c45c3a3cf0d1610e479433a215a30cf301f0603551d2304", + "1830168014c8ade9774c45c3a3cf0d1610e479433a215a30cf300f0603551d13", + "0101ff040530030101ff300e0603551d0f0101ff040403020284300a06082a86", + "48ce3d040302034700304402203521a3ef8b34461e9cd560f31d5889206adca3", + "6541f60d9ece8a198c6648607b02204d0bf351d9307c7d5bda35341da8471b63", + "a585653cad4f24a7e74daf417df1bf", +); + +/// Per-algorithm attestation certificate signing information. +pub struct CertSignAlgoInfo { + key: KeyMaterial, + chain: Vec, +} + +/// Certificate signing information for all asymmetric key types. +pub struct CertSignInfo { + rsa_info: CertSignAlgoInfo, + ec_info: CertSignAlgoInfo, +} + +impl CertSignInfo { + /// Create a new cert signing impl. + pub fn new() -> Self { + CertSignInfo { + rsa_info: CertSignAlgoInfo { + key: KeyMaterial::Rsa(rsa::Key(hex::decode(RSA_ATTEST_KEY).unwrap()).into()), + chain: vec![ + keymint::Certificate { + encoded_certificate: hex::decode(RSA_ATTEST_CERT).unwrap(), + }, + keymint::Certificate { + encoded_certificate: hex::decode(RSA_ATTEST_ROOT_CERT).unwrap(), + }, + ], + }, + ec_info: CertSignAlgoInfo { + key: KeyMaterial::Ec( + EcCurve::P256, + CurveType::Nist, + ec::Key::P256(ec::NistKey(hex::decode(EC_ATTEST_KEY).unwrap())).into(), + ), + chain: vec![ + keymint::Certificate { + encoded_certificate: hex::decode(EC_ATTEST_CERT).unwrap(), + }, + keymint::Certificate { + encoded_certificate: hex::decode(EC_ATTEST_ROOT_CERT).unwrap(), + }, + ], + }, + } + } +} + +impl RetrieveCertSigningInfo for CertSignInfo { + fn signing_key(&self, key_type: SigningKeyType) -> Result { + Ok(match key_type.algo_hint { + SigningAlgorithm::Rsa => self.rsa_info.key.clone(), + SigningAlgorithm::Ec => self.ec_info.key.clone(), + }) + } + + fn cert_chain(&self, key_type: SigningKeyType) -> Result, Error> { + Ok(match key_type.algo_hint { + SigningAlgorithm::Rsa => self.rsa_info.chain.clone(), + SigningAlgorithm::Ec => self.ec_info.chain.clone(), + }) + } +} diff --git a/libs/rust/src/clock.rs b/libs/rust/src/clock.rs new file mode 100644 index 0000000..6e9759a --- /dev/null +++ b/libs/rust/src/clock.rs @@ -0,0 +1,45 @@ +// +// Copyright (C) 2022 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. + +//! Monotonic clock implementation. + +use kmr_common::crypto; +use log::warn; + +/// Monotonic clock. +pub struct StdClock; + +impl StdClock { + /// Create new clock instance, holding time since construction. + pub fn new() -> Self { + Self {} + } +} + +impl crypto::MonotonicClock for StdClock { + fn now(&self) -> crypto::MillisecondsSinceEpoch { + let mut time = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + // Use `CLOCK_BOOTTIME` for consistency with the times used by the Cuttlefish + // C++ implementation of Gatekeeper. + let rc = + // Safety: `time` is a valid structure. + unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut time as *mut libc::timespec) }; + if rc < 0 { + warn!("failed to get time!"); + return crypto::MillisecondsSinceEpoch(0); + } + crypto::MillisecondsSinceEpoch(((time.tv_sec * 1000) + (time.tv_nsec / 1000 / 1000)).into()) + } +} diff --git a/libs/rust/src/lib.rs b/libs/rust/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/libs/rust/src/logging.rs b/libs/rust/src/logging.rs new file mode 100644 index 0000000..502f9ce --- /dev/null +++ b/libs/rust/src/logging.rs @@ -0,0 +1,18 @@ +use log::LevelFilter; +use log4rs::append::console::ConsoleAppender; +use log4rs::config::{Appender, Config, Root}; +use log4rs::encode::pattern::PatternEncoder; + +const PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S %Z)(utc)} [{h({l})}] {M} - {m}{n}"; + +pub fn init_logger() { + let stdout = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new(PATTERN))) + .build(); + let root = Root::builder().appender("stdout").build(LevelFilter::Debug); + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .build(root) + .unwrap(); + log4rs::init_config(config).unwrap(); +} diff --git a/libs/rust/src/macros.rs b/libs/rust/src/macros.rs new file mode 100644 index 0000000..f0be221 --- /dev/null +++ b/libs/rust/src/macros.rs @@ -0,0 +1,31 @@ +#[macro_export] +#[cfg(target_os = "android")] +macro_rules! logd { + ($tag:expr, $msg:expr) => { + android_logger_lite::d($tag.to_string(), $msg.to_string()) + }; +} + +#[macro_export] +#[cfg(target_os = "android")] +macro_rules! logi { + ($tag:expr, $msg:expr) => { + android_logger_lite::i($tag.to_string(), $msg.to_string()) + }; +} + +#[macro_export] +#[cfg(target_os = "android")] +macro_rules! logw { + ($tag:expr, $msg:expr) => { + android_logger_lite::w($tag.to_string(), $msg.to_string()) + }; +} + +#[macro_export] +#[cfg(target_os = "android")] +macro_rules! loge { + ($tag:expr, $msg:expr) => { + android_logger_lite::e($tag.to_string(), $msg.to_string()) + }; +} diff --git a/libs/rust/src/main.rs b/libs/rust/src/main.rs new file mode 100644 index 0000000..5d03806 --- /dev/null +++ b/libs/rust/src/main.rs @@ -0,0 +1,209 @@ +use std::fmt::Debug; + +use kmr_common::crypto::{self, des::Key, MonotonicClock}; +use kmr_crypto_boring::{ + aes::BoringAes, aes_cmac::BoringAesCmac, des::BoringDes, ec::BoringEc, eq::BoringEq, hmac::BoringHmac, rng::BoringRng, rsa::BoringRsa, sha256::BoringSha256 +}; +use kmr_ta::{device::{CsrSigningAlgorithm, Implementation}, HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; +use kmr_wire::{cbor::de, keymint::{AttestationKey, DateTime, KeyCharacteristics, KeyParam, KeyPurpose, SecurityLevel}, rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR, sharedsecret::SharedSecretParameters, GenerateKeyRequest, GetHardwareInfoRequest, GetRootOfTrustRequest, GetSharedSecretParametersRequest, KeySizeInBits, PerformOpReq}; +use log::{debug, error}; +use kmr_wire::AsCborValue; + +pub mod macros; +pub mod attest; +pub mod sdd; +pub mod clock; +pub mod proto; +pub mod soft; +pub mod rpc; +pub mod logging; + +#[cfg(target_os = "android")] +const TAG: &str = "OhMyKeymint"; + +fn main(){ + debug!("Hello, OhMyKeymint!"); + logging::init_logger(); + #[cfg(target_os = "android")] logi!(TAG, "Application started"); + let security_level = SecurityLevel::TrustedEnvironment; + let hw_info = HardwareInfo { + version_number: 2, + security_level, + impl_name: "Qualcomm QTEE KeyMint 2", + author_name: "Qualcomm Technologies", + unique_id: "Qualcomm QTEE KeyMint 2", + }; + + let rpc_sign_algo = CsrSigningAlgorithm::EdDSA; + let rpc_info_v3 = RpcInfoV3 { + author_name: "Qualcomm Technologies", + unique_id: "Qualcomm QTEE KeyMint 2", + fused: false, + supported_num_of_keys_in_csr: MINIMUM_SUPPORTED_KEYS_IN_CSR, + }; + + let mut rng = BoringRng; + let sdd_mgr: Option> = + match sdd::HostSddManager::new(&mut rng) { + Ok(v) => Some(Box::new(v)), + Err(e) => { + error!("Failed to initialize secure deletion data manager: {:?}", e); + None + } + }; + let clock = clock::StdClock; + let rsa = BoringRsa::default(); + let ec = BoringEc::default(); + let hkdf: Box = Box::new(BoringHmac); + let imp = crypto::Implementation { + rng: Box::new(rng), + clock: Some(Box::new(clock)), + compare: Box::new(BoringEq), + aes: Box::new(BoringAes), + des: Box::new(BoringDes), + hmac: Box::new(BoringHmac), + rsa: Box::new(rsa), + ec: Box::new(ec), + ckdf: Box::new(BoringAesCmac), + hkdf, + sha256: Box::new(BoringSha256), + }; + + let keys: Box = Box::new(soft::Keys); + let rpc: Box = Box::new(soft::RpcArtifacts::new(soft::Derive::default(), rpc_sign_algo)); + + let dev = Implementation { + keys, + // Cuttlefish has `remote_provisioning.tee.rkp_only=1` so don't support batch signing + // of keys. This can be reinstated with: + // ``` + // sign_info: Some(kmr_ta_nonsecure::attest::CertSignInfo::new()), + // ``` + sign_info: Some(Box::new(attest::CertSignInfo::new())), + // HAL populates attestation IDs from properties. + attest_ids: None, + sdd_mgr, + // `BOOTLOADER_ONLY` keys not supported. + bootloader: Box::new(kmr_ta::device::BootloaderDone), + // `STORAGE_KEY` keys not supported. + sk_wrapper: None, + // `TRUSTED_USER_PRESENCE_REQUIRED` keys not supported + tup: Box::new(kmr_ta::device::TrustedPresenceUnsupported), + // No support for converting previous implementation's keyblobs. + legacy_key: None, + rpc, + }; + + let mut ta = KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev); + + let req = PerformOpReq::DeviceGetHardwareInfo(GetHardwareInfoRequest{}); + let resp = ta.process_req(req); + debug!("GetHardwareInfo response: {:?}", resp); + + let req = PerformOpReq::SetBootInfo(kmr_wire::SetBootInfoRequest { + verified_boot_state: 0, // Verified + verified_boot_hash: vec![0; 32], + verified_boot_key: vec![0; 32], + device_boot_locked: true, + boot_patchlevel: 20250605, + }); + let resp = ta.process_req(req); + debug!("SetBootInfo response: {:?}", resp); + + let req = PerformOpReq::SetHalInfo(kmr_wire::SetHalInfoRequest { + os_version: 35, + os_patchlevel: 202506, + vendor_patchlevel: 202506, + }); + let resp = ta.process_req(req); + debug!("SetHalInfo response: {:?}", resp); + + let req = PerformOpReq::SetHalVersion(kmr_wire::SetHalVersionRequest { + aidl_version: 400, + }); + let resp = ta.process_req(req); + debug!("SetHalVersion response: {:?}", resp); + + let req = PerformOpReq::SetAttestationIds(kmr_wire::SetAttestationIdsRequest { + ids: kmr_wire::AttestationIdInfo { + brand: "generic".into(), + device: "generic".into(), + product: "generic".into(), + serial: "0123456789ABCDEF".into(), + manufacturer: "Generic".into(), + model: "GenericModel".into(), + imei: "350505563694821".into(), + imei2: "350505563694822".into(), + meid: "350505563694823".into(), + } + }); + let resp = ta.process_req(req); + debug!("SetAttestationIds response: {:?}", resp); + + let req = PerformOpReq::DeviceEarlyBootEnded(kmr_wire::EarlyBootEndedRequest{}); + let resp = ta.process_req(req); + debug!("DeviceEarlyBootEnded response: {:?}", resp); + + let clock = clock::StdClock; + let current_time = clock.now().0; + let keyblob = kmr_common::keyblob::EncryptedKeyBlobV1 { + characteristics: vec![KeyCharacteristics { + security_level: SecurityLevel::TrustedEnvironment, + authorizations: vec![ + KeyParam::ApplicationId("com.example.app".as_bytes().to_vec()), + KeyParam::AttestationApplicationId("com.example.app".as_bytes().to_vec()), + KeyParam::Purpose(KeyPurpose::AttestKey), + KeyParam::KeySize(KeySizeInBits(256)), + KeyParam::Algorithm(kmr_wire::keymint::Algorithm::Ec), + KeyParam::EcCurve(kmr_wire::keymint::EcCurve::P256), + KeyParam::Digest(kmr_wire::keymint::Digest::Sha256), + KeyParam::NoAuthRequired, + KeyParam::CertificateNotBefore(DateTime{ms_since_epoch: clock.now().0 - 10000}), // -10 seconds + KeyParam::CertificateNotAfter(DateTime{ms_since_epoch: clock.now().0 + 31536000000}), // +1 year + KeyParam::CertificateSerial(b"1234567890".to_vec()), + KeyParam::CertificateSubject(b"CN=Android Keystore Key".to_vec()), + KeyParam::AttestationChallenge(b"Test Attestation Challenge".to_vec()), + + ], + }], + key_derivation_input: [0u8; 32], + kek_context: vec![0; 32], + encrypted_key_material: kmr_wire::coset::CoseEncrypt0::default(), + secure_deletion_slot: None, + }; + + let req = PerformOpReq::DeviceGenerateKey(GenerateKeyRequest{ + key_params: vec![ + KeyParam::ApplicationId("com.example.app".as_bytes().to_vec()), + KeyParam::AttestationApplicationId("com.example.app".as_bytes().to_vec()), + KeyParam::Purpose(KeyPurpose::AttestKey), + KeyParam::KeySize(KeySizeInBits(256)), + KeyParam::Algorithm(kmr_wire::keymint::Algorithm::Ec), + KeyParam::EcCurve(kmr_wire::keymint::EcCurve::P256), + KeyParam::Digest(kmr_wire::keymint::Digest::Sha256), + KeyParam::NoAuthRequired, + KeyParam::CertificateNotBefore(DateTime{ms_since_epoch: current_time - 10000}), // -10 seconds + KeyParam::CertificateNotAfter(DateTime{ms_since_epoch: current_time + 31536000000}), // +1 year + KeyParam::CertificateSerial(b"1234567890".to_vec()), + KeyParam::CertificateSubject(kmr_wire::keymint::DEFAULT_CERT_SUBJECT.to_vec()), + KeyParam::AttestationChallenge(b"Test Attestation Challenge".to_vec()), + + ], + attestation_key: None, + }); + let resp = ta.process_req(req); + match &resp.rsp { + Some(rsp) => { + if let kmr_wire::PerformOpRsp::DeviceGenerateKey(ref key_rsp) = rsp { + std::fs::create_dir_all("./omk/output").unwrap(); + std::fs::write("./omk/output/cert.der", key_rsp.ret.certificate_chain[0].encoded_certificate.clone()).unwrap(); + } else { + error!("Unexpected response: {:?}", resp); + } + }, + None => { + error!("No response received"); + } + } + +} \ No newline at end of file diff --git a/libs/rust/src/rpc.rs b/libs/rust/src/rpc.rs new file mode 100644 index 0000000..39da50e --- /dev/null +++ b/libs/rust/src/rpc.rs @@ -0,0 +1,234 @@ +// +// Copyright (C) 2022 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. + +//! Emulated implementation of device traits for `IRemotelyProvisionedComponent`. + +use core::cell::RefCell; +use kmr_common::crypto::{ec, ec::CoseKeyPurpose, Ec, KeyMaterial}; +use kmr_common::{crypto, explicit, rpc_err, vec_try, Error}; +use kmr_crypto_boring::{ec::BoringEc, hmac::BoringHmac, rng::BoringRng}; +use kmr_ta::device::{ + CsrSigningAlgorithm, DiceInfo, PubDiceArtifacts, RetrieveRpcArtifacts, RpcV2Req, +}; +use kmr_wire::coset::{iana, CoseSign1Builder, HeaderBuilder}; +use kmr_wire::keymint::{Digest, EcCurve}; +use kmr_wire::{cbor::value::Value, coset::AsCborValue, rpc, CborError}; + +/// Trait to encapsulate deterministic derivation of secret data. +pub trait DeriveBytes { + /// Derive `output_len` bytes of data from `context`, deterministically. + fn derive_bytes(&self, context: &[u8], output_len: usize) -> Result, Error>; +} + +/// Common emulated implementation of RPC artifact retrieval. +pub struct Artifacts { + derive: T, + sign_algo: CsrSigningAlgorithm, + // Invariant once populated: `self.dice_info.signing_algorithm` == `self.sign_algo` + dice_info: RefCell>, + // Invariant once populated: `self.bcc_signing_key` is a variant that matches `self.sign_algo` + bcc_signing_key: RefCell>, +} + +impl RetrieveRpcArtifacts for Artifacts { + fn derive_bytes_from_hbk( + &self, + _hkdf: &dyn crypto::Hkdf, + context: &[u8], + output_len: usize, + ) -> Result, Error> { + self.derive.derive_bytes(context, output_len) + } + + fn get_dice_info(&self, _test_mode: rpc::TestMode) -> Result { + if self.dice_info.borrow().is_none() { + let (dice_info, priv_key) = self.generate_dice_artifacts(rpc::TestMode(false))?; + *self.dice_info.borrow_mut() = Some(dice_info); + *self.bcc_signing_key.borrow_mut() = Some(priv_key); + } + + Ok(self + .dice_info + .borrow() + .as_ref() + .ok_or_else(|| rpc_err!(Failed, "DICE artifacts are not initialized."))? + .clone()) + } + + fn sign_data( + &self, + ec: &dyn crypto::Ec, + data: &[u8], + _rpc_v2: Option, + ) -> Result, Error> { + // DICE artifacts should have been initialized via `get_dice_info()` by the time this + // method is called. + let private_key = self + .bcc_signing_key + .borrow() + .as_ref() + .ok_or_else(|| rpc_err!(Failed, "DICE artifacts are not initialized."))? + .clone(); + + let mut op = ec.begin_sign(private_key.into(), self.signing_digest())?; + op.update(data)?; + let sig = op.finish()?; + crypto::ec::to_cose_signature(self.signing_curve(), sig) + } +} + +impl Artifacts { + /// Constructor. + pub fn new(derive: T, sign_algo: CsrSigningAlgorithm) -> Self { + Self { + derive, + sign_algo, + dice_info: RefCell::new(None), + bcc_signing_key: RefCell::new(None), + } + } + + /// Indicate the curve used in signing. + fn signing_curve(&self) -> EcCurve { + match self.sign_algo { + CsrSigningAlgorithm::ES256 => EcCurve::P256, + CsrSigningAlgorithm::ES384 => EcCurve::P384, + CsrSigningAlgorithm::EdDSA => EcCurve::Curve25519, + } + } + + /// Indicate the digest used in signing. + fn signing_digest(&self) -> Digest { + match self.sign_algo { + CsrSigningAlgorithm::ES256 => Digest::Sha256, + CsrSigningAlgorithm::ES384 => Digest::Sha384, + CsrSigningAlgorithm::EdDSA => Digest::None, + } + } + + /// Indicate the COSE algorithm value associated with signing. + fn signing_cose_algo(&self) -> iana::Algorithm { + match self.sign_algo { + CsrSigningAlgorithm::ES256 => iana::Algorithm::ES256, + CsrSigningAlgorithm::ES384 => iana::Algorithm::ES384, + CsrSigningAlgorithm::EdDSA => iana::Algorithm::EdDSA, + } + } + + fn generate_dice_artifacts( + &self, + _test_mode: rpc::TestMode, + ) -> Result<(DiceInfo, ec::Key), Error> { + let ec = BoringEc::default(); + + let key_material = match self.sign_algo { + CsrSigningAlgorithm::EdDSA => { + let secret = self.derive_bytes_from_hbk(&BoringHmac, b"Device Key Seed", 32)?; + ec::import_raw_ed25519_key(&secret) + } + // TODO: generate the *same* key after reboot, by use of the TPM. + CsrSigningAlgorithm::ES256 => { + ec.generate_nist_key(&mut BoringRng, ec::NistCurve::P256, &[]) + } + CsrSigningAlgorithm::ES384 => { + ec.generate_nist_key(&mut BoringRng, ec::NistCurve::P384, &[]) + } + }?; + let (pub_cose_key, private_key) = match key_material { + KeyMaterial::Ec(curve, curve_type, key) => ( + key.public_cose_key( + &ec, + curve, + curve_type, + CoseKeyPurpose::Sign, + None, /* no key ID */ + rpc::TestMode(false), + )?, + key, + ), + _ => { + return Err(rpc_err!( + Failed, + "expected the Ec variant of KeyMaterial for the cdi leaf key." + )) + } + }; + + let cose_key_cbor = pub_cose_key.to_cbor_value().map_err(CborError::from)?; + let cose_key_cbor_data = kmr_ta::rkp::serialize_cbor(&cose_key_cbor)?; + + // Construct `DiceChainEntryPayload` + let dice_chain_entry_payload = Value::Map(vec_try![ + // Issuer + ( + Value::Integer(1.into()), + Value::Text(String::from("Issuer")) + ), + // Subject + ( + Value::Integer(2.into()), + Value::Text(String::from("Subject")) + ), + // Subject public key + ( + Value::Integer((-4670552).into()), + Value::Bytes(cose_key_cbor_data) + ), + // Key Usage field contains a CBOR byte string of the bits which correspond + // to `keyCertSign` as per RFC 5280 Section 4.2.1.3 (in little-endian byte order) + ( + Value::Integer((-4670553).into()), + Value::Bytes(vec_try![0x20]?) + ), + ]?); + let dice_chain_entry_payload_data = kmr_ta::rkp::serialize_cbor(&dice_chain_entry_payload)?; + + // Construct `DiceChainEntry` + let protected = HeaderBuilder::new() + .algorithm(self.signing_cose_algo()) + .build(); + let dice_chain_entry = CoseSign1Builder::new() + .protected(protected) + .payload(dice_chain_entry_payload_data) + .try_create_signature(&[], |input| { + let mut op = ec.begin_sign(private_key.clone(), self.signing_digest())?; + op.update(input)?; + let sig = op.finish()?; + crypto::ec::to_cose_signature(self.signing_curve(), sig) + })? + .build(); + let dice_chain_entry_cbor = dice_chain_entry.to_cbor_value().map_err(CborError::from)?; + + // Construct `DiceCertChain` + let dice_cert_chain = Value::Array(vec_try![cose_key_cbor, dice_chain_entry_cbor]?); + let dice_cert_chain_data = kmr_ta::rkp::serialize_cbor(&dice_cert_chain)?; + + // Construct `UdsCerts` as an empty CBOR map + let uds_certs_data = kmr_ta::rkp::serialize_cbor(&Value::Map(Vec::new()))?; + + let pub_dice_artifacts = PubDiceArtifacts { + dice_cert_chain: dice_cert_chain_data, + uds_certs: uds_certs_data, + }; + + let dice_info = DiceInfo { + pub_dice_artifacts, + signing_algorithm: self.sign_algo, + rpc_v2_test_cdi_priv: None, + }; + + Ok((dice_info, explicit!(private_key)?)) + } +} diff --git a/libs/rust/src/sdd.rs b/libs/rust/src/sdd.rs new file mode 100644 index 0000000..9e8f05f --- /dev/null +++ b/libs/rust/src/sdd.rs @@ -0,0 +1,244 @@ +// Copyright (C) 2023 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. + +//! Secure deletion data manager for Cuttlefish. +//! This implementetation is "secure" in the sense that the underlying storage can not be accessed +//! by Android. However, it is does not provide any protections against the host, i.e. anyone with +//! access to the host can read and alter the contents of deletion data. + +use crate::proto::storage; +use kmr_common::{crypto, keyblob, km_err, Error}; +use log::info; +use prost::Message; +use std::fs; +use std::io::BufRead; +use std::io::Write; +use std::path; + +use crate::error; + +const SECURE_DELETION_DATA_FILE: &str = "./omk/data/keymint_secure_deletion_data"; + +#[cfg(target_os = "android")] +const SECURE_DELETION_DATA_FILE: &str = "/data/adb/omk/data/keymint_secure_deletion_data"; + +fn read_sdd_file() -> Result { + let f = fs::File::open(SECURE_DELETION_DATA_FILE).map_err(|e| { + km_err!( + SecureHwCommunicationFailed, + "failed to open secure deletion data file: {:?}", + e + ) + })?; + let mut buf = std::io::BufReader::new(f); + let mut buf = buf.fill_buf().map_err(|e| { + km_err!( + SecureHwCommunicationFailed, + "failed to read secure deletion data file: {:?}", + e + ) + })?; + storage::SecureDeletionData::decode(&mut buf).map_err(|e| { + km_err!( + SecureHwCommunicationFailed, + "failed to parse secure deletion data: {:?}", + e + ) + }) +} + +fn write_sdd_file(data: &storage::SecureDeletionData) -> Result<(), Error> { + fs::create_dir_all(path::Path::new(SECURE_DELETION_DATA_FILE).parent().unwrap()) + .map_err(|e| { + km_err!( + SecureHwCommunicationFailed, + "failed to create directory for secure deletion data file: {:?}", + e + ) + })?; + let mut f = fs::File::create(SECURE_DELETION_DATA_FILE).map_err(|e| { + km_err!( + SecureHwCommunicationFailed, + "failed to create secure deletion data file: {:?}", + e + ) + })?; + let mut buf = Vec::with_capacity(data.encoded_len()); + data.encode(&mut buf).map_err(|e| { + km_err!( + SecureHwCommunicationFailed, + "failed to write to secure deletion data file: {:?}", + e + ) + })?; + f.write_all(&buf).map_err(|e| { + km_err!( + SecureHwCommunicationFailed, + "failed to write to secure deletion data file: {:?}", + e + ) + })?; + Ok(()) +} + +pub struct HostSddManager { + // Local cache of data stored on disk. + data: storage::SecureDeletionData, +} + +impl HostSddManager { + fn init(&mut self, rng: &mut dyn crypto::Rng) -> Result<(), Error> { + // Restore data from disk if it was previously saved. + if path::Path::new(SECURE_DELETION_DATA_FILE).exists() { + info!("Secure deletion data file found. Parsing."); + self.data = read_sdd_file()?; + return Ok(()); + } + + info!("No secure deletion data file found. Creating one."); + + // Initialize factory reset secret. + self.data.factory_secret.resize(32, 0); + rng.fill_bytes(&mut self.data.factory_secret[..]); + + // Create secure deletion data file. + write_sdd_file(&self.data) + } + + pub fn new(rng: &mut dyn crypto::Rng) -> Result { + let mut sdd_mgr = Self { + data: storage::SecureDeletionData::default(), + }; + sdd_mgr.init(rng).map(|_| sdd_mgr) + } +} + +impl keyblob::SecureDeletionSecretManager for HostSddManager { + fn get_or_create_factory_reset_secret( + &mut self, + rng: &mut dyn crypto::Rng, + ) -> Result { + if self.data.factory_secret.is_empty() { + self.init(rng)?; + } + self.get_factory_reset_secret() + } + + fn get_factory_reset_secret(&self) -> Result { + if self.data.factory_secret.is_empty() { + return Err(km_err!(UnknownError, "no factory secret available")); + } + Ok(keyblob::SecureDeletionData { + factory_reset_secret: self.data.factory_secret.clone().try_into().unwrap(), + secure_deletion_secret: [0; 16], + }) + } + + fn new_secret( + &mut self, + rng: &mut dyn crypto::Rng, + _purpose: keyblob::SlotPurpose, + ) -> Result<(keyblob::SecureDeletionSlot, keyblob::SecureDeletionData), Error> { + // Allocate new slot ID. + let slot_id = self.data.last_free_slot.checked_add(1).ok_or(km_err!( + RollbackResistanceUnavailable, + "ran out of slot IDs" + ))?; + + info!("Generating new secret with slot ID: {:?}", slot_id); + + assert!( + !self.data.secure_deletion_secrets.contains_key(&slot_id), + "Slot ID already in use: {:?}", + slot_id + ); + + // Generate new sdd. + let mut sdd = self.get_or_create_factory_reset_secret(rng)?; + rng.fill_bytes(&mut sdd.secure_deletion_secret[..]); + + // Cache the secure deletion secret locally. + self.data + .secure_deletion_secrets + .insert(slot_id, sdd.secure_deletion_secret.to_vec()); + self.data.last_free_slot = slot_id; + + // Save the secure deletion secret on disk. + match write_sdd_file(&self.data) { + Ok(_) => Ok((keyblob::SecureDeletionSlot(slot_id), sdd)), + Err(e) => { + // Restore cached state. + self.data.secure_deletion_secrets.remove(&slot_id).unwrap(); + self.data.last_free_slot = slot_id - 1; + Err(e) + } + } + } + + fn get_secret( + &self, + slot: keyblob::SecureDeletionSlot, + ) -> Result { + let slot_id = slot.0; + info!("Fetching secret with slot ID: {:?}", slot_id); + + let secret = self + .data + .secure_deletion_secrets + .get(&slot_id) + .ok_or(km_err!(InvalidKeyBlob, "slot ID: {:?} not found.", slot_id))?; + Ok(keyblob::SecureDeletionData { + factory_reset_secret: self.data.factory_secret.clone().try_into().unwrap(), + secure_deletion_secret: secret.clone().try_into().unwrap(), + }) + } + + fn delete_secret(&mut self, slot: keyblob::SecureDeletionSlot) -> Result<(), Error> { + let slot_id = slot.0; + info!("Deleting secret with slot ID: {:?}", slot_id); + + let secret = self + .data + .secure_deletion_secrets + .remove(&slot_id) + .ok_or(km_err!(InvalidKeyBlob, "slot ID not found."))?; + + // Save the secure deletion secret on disk. + if let Err(e) = write_sdd_file(&self.data) { + // Restore cached state. + self.data + .secure_deletion_secrets + .insert(slot_id, secret) + .unwrap(); + return Err(e); + } + Ok(()) + } + + fn delete_all(&mut self) { + info!("Deleting all secrets"); + self.data = storage::SecureDeletionData::default(); + if path::Path::new(SECURE_DELETION_DATA_FILE).exists() { + // We want to guarantee that if this function returns, all secrets have been + // successfully deleted. So, panic if we fail to delete the file. + for _ in 0..5 { + match fs::remove_file(SECURE_DELETION_DATA_FILE) { + Ok(_) => return, + Err(e) => error!("Couldn't delete file: {:?}", e), + } + } + panic!("FATAL: Failed to delete secure deletion data file."); + } + } +} diff --git a/libs/rust/src/soft.rs b/libs/rust/src/soft.rs new file mode 100644 index 0000000..73a08cf --- /dev/null +++ b/libs/rust/src/soft.rs @@ -0,0 +1,67 @@ +// +// Copyright (C) 2022 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. + +//! Software-only trait implementations using fake keys. + +use kmr_common::{ + crypto, + crypto::{Hkdf, Rng}, + Error, +}; +use kmr_crypto_boring::{hmac::BoringHmac, rng::BoringRng}; +use kmr_ta::device::RetrieveKeyMaterial; + +/// Root key retrieval using hard-coded fake keys. +pub struct Keys; + +impl RetrieveKeyMaterial for Keys { + fn root_kek(&self, _context: &[u8]) -> Result, Error> { + // Matches `MASTER_KEY` in system/keymaster/key_blob_utils/software_keyblobs.cpp + Ok(crypto::hmac::Key::new([0; 16].to_vec()).into()) + } + fn kak(&self) -> Result, Error> { + // Matches `kFakeKeyAgreementKey` in + // system/keymaster/km_openssl/soft_keymaster_enforcement.cpp. + Ok(crypto::aes::Key::Aes256([0; 32]).into()) + } + fn unique_id_hbk(&self, _ckdf: &dyn crypto::Ckdf) -> Result { + // Matches value used in system/keymaster/contexts/pure_soft_keymaster_context.cpp. + crypto::hmac::Key::new_from(b"Very Very Secret HKDF Key") + } +} + +/// Implementation of key derivation using a random fake key. +pub struct Derive { + hbk: Vec, +} + +impl Default for Derive { + fn default() -> Self { + // Use random data as an emulation of a hardware-backed key. + let mut hbk = vec![0; 32]; + let mut rng = BoringRng; + rng.fill_bytes(&mut hbk); + Self { hbk } + } +} + +impl crate::rpc::DeriveBytes for Derive { + fn derive_bytes(&self, context: &[u8], output_len: usize) -> Result, Error> { + BoringHmac.hkdf(&[], &self.hbk, context, output_len) + } +} + +/// RPC artifact retrieval using software fake key. +pub type RpcArtifacts = crate::rpc::Artifacts; diff --git a/libs/rust/ta/Cargo.toml b/libs/rust/ta/Cargo.toml new file mode 100644 index 0000000..a05ccf9 --- /dev/null +++ b/libs/rust/ta/Cargo.toml @@ -0,0 +1,31 @@ +# Note that Cargo is not an officially supported build tool (Android's Soong is the official +# tool). This Cargo.toml file is included purely for the convenience of KeyMint developers. + +[package] +name = "kmr-ta" +version = "0.1.0" +authors = ["David Drysdale "] +edition = "2021" +license = "Apache-2.0" + +[features] +default = [] +# The `downgrade` feature allows the HAL service to tell the TA what version of the KeyMint +# HAL to implement. +downgrade = [] + +[dependencies] +ciborium = { version = "^0.2.0", default-features = false } +ciborium-io = "^0.2.0" +coset = "0.3.3" +der = { version = "^0.7.8", features = ["alloc", "derive"] } +flagset = "0.4.3" +kmr-common = "*" +kmr-derive = "*" +kmr-wire = "*" +log = "^0.4" +spki = { version = "0.7.3"} +x509-cert = { version = "0.2.4", default-features = false } + +[dev-dependencies] +hex = "0.4.3" diff --git a/libs/rust/ta/fuzz/.gitignore b/libs/rust/ta/fuzz/.gitignore new file mode 100644 index 0000000..1a45eee --- /dev/null +++ b/libs/rust/ta/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/libs/rust/ta/fuzz/Cargo.toml b/libs/rust/ta/fuzz/Cargo.toml new file mode 100644 index 0000000..c28d135 --- /dev/null +++ b/libs/rust/ta/fuzz/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "kmr-ta-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +der = { version = "^0.7.8", features = ["alloc", "derive"] } +libfuzzer-sys = "0.4" + +[dependencies.kmr-ta] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "keydescription" +path = "fuzz_targets/keydescription.rs" +test = false +doc = false +bench = false + +[patch.crates-io] +kmr-common = { path = "../../common" } +kmr-derive = { path = "../../derive" } +kmr-wire = { path = "../../wire" } diff --git a/libs/rust/ta/fuzz/fuzz_targets/keydescription.rs b/libs/rust/ta/fuzz/fuzz_targets/keydescription.rs new file mode 100644 index 0000000..3e4d129 --- /dev/null +++ b/libs/rust/ta/fuzz/fuzz_targets/keydescription.rs @@ -0,0 +1,9 @@ +//! Fuzzer for parsing ASN.1 key descriptions. +#![no_main] + +use der::Decode; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _result = kmr_ta::keys::SecureKeyWrapper::from_der(data); +}); diff --git a/libs/rust/ta/src/cert.rs b/libs/rust/ta/src/cert.rs new file mode 100644 index 0000000..2956108 --- /dev/null +++ b/libs/rust/ta/src/cert.rs @@ -0,0 +1,1602 @@ +// Copyright 2022, 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. + +//! Generation of certificates and attestation extensions. + +use crate::keys::SigningInfo; +use core::time::Duration; +use std::borrow::Cow; +use der::asn1::{BitString, OctetString, OctetStringRef, SetOfVec}; +use der::{ + asn1::{GeneralizedTime, Null, UtcTime}, + oid::AssociatedOid, + Enumerated, Sequence, +}; +use der::{Decode, Encode, EncodeValue, ErrorKind, Length}; +use flagset::FlagSet; +use kmr_common::crypto::KeyMaterial; +use kmr_common::{ + crypto, der_err, get_tag_value, km_err, tag, try_to_vec, vec_try_with_capacity, Error, +}; +use kmr_common::{get_bool_tag_value, get_opt_tag_value, FallibleAllocExt}; +use kmr_wire::{ + keymint, + keymint::{ + from_raw_tag_value, raw_tag_value, DateTime, ErrorCode, KeyCharacteristics, KeyParam, + KeyPurpose, Tag, + }, + KeySizeInBits, RsaExponent, +}; +use spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfoOwned}; +use x509_cert::serial_number::SerialNumber; +use x509_cert::{ + certificate::{Certificate, TbsCertificate, Version}, + ext::pkix::{constraints::BasicConstraints, KeyUsage, KeyUsages}, + ext::Extension, + name::RdnSequence, + time::Time, +}; + +/// OID value for the Android Attestation extension. +pub const ATTESTATION_EXTENSION_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11129.2.1.17"); + +/// Empty value to use in the `RootOfTrust.verifiedBootKey` field in attestations +/// if an empty value was passed to the bootloader. +const EMPTY_BOOT_KEY: [u8; 32] = [0u8; 32]; + +/// Build an ASN.1 DER-encodable `Certificate`. +pub(crate) fn certificate(tbs_cert: TbsCertificate, sig_val: &[u8]) -> Result { + Ok(Certificate { + signature_algorithm: tbs_cert.signature.clone(), + tbs_certificate: tbs_cert, + signature: BitString::new(0, sig_val) + .map_err(|e| der_err!(e, "failed to build BitString"))?, + }) +} + +/// Build an ASN.1 DER-encodable `tbsCertificate`. +pub(crate) fn tbs_certificate<'a>( + info: &'a Option, + spki: SubjectPublicKeyInfoOwned, + key_usage_ext_bits: &'a [u8], + basic_constraint_ext_val: Option<&'a [u8]>, + attestation_ext: Option<&'a [u8]>, + chars: &'a [KeyParam], + params: &'a [KeyParam], +) -> Result { + let cert_serial = tag::get_cert_serial(params)?; + let cert_subject = tag::get_cert_subject(params)?; + let not_before = get_tag_value!(params, CertificateNotBefore, ErrorCode::MissingNotBefore)?; + let not_after = get_tag_value!(params, CertificateNotAfter, ErrorCode::MissingNotAfter)?; + + // Determine the OID part of the `AlgorithmIdentifier`; we do not support any signing key + // types that have parameters in the `AlgorithmIdentifier` + let sig_alg_oid = match info { + Some(info) => match info.signing_key { + KeyMaterial::Rsa(_) => crypto::rsa::SHA256_PKCS1_SIGNATURE_OID, + KeyMaterial::Ec(curve, _, _) => crypto::ec::curve_to_signing_oid(curve), + _ => { + return Err(km_err!(UnsupportedAlgorithm, "unexpected cert signing key type")); + } + }, + None => { + // No signing key, so signature will be empty, but we still need a value here. + match tag::get_algorithm(params)? { + keymint::Algorithm::Rsa => crypto::rsa::SHA256_PKCS1_SIGNATURE_OID, + keymint::Algorithm::Ec => { + crypto::ec::curve_to_signing_oid(tag::get_ec_curve(chars)?) + } + alg => { + return Err(km_err!( + UnsupportedAlgorithm, + "unexpected algorithm for public key {:?}", + alg + )) + } + } + } + }; + let cert_issuer = match &info { + Some(info) => &info.issuer_subject, + None => cert_subject, + }; + + // Build certificate extensions + let key_usage_extension = Extension { + extn_id: KeyUsage::OID, + critical: true, + extn_value: OctetString::new(key_usage_ext_bits) + .map_err(|e| der_err!(e, "failed to build OctetString"))?, + }; + + let mut cert_extensions = vec_try_with_capacity!(3)?; + cert_extensions.push(key_usage_extension); // capacity enough + + if let Some(basic_constraint_ext_val) = basic_constraint_ext_val { + let basic_constraint_ext = Extension { + extn_id: BasicConstraints::OID, + critical: true, + extn_value: OctetString::new(basic_constraint_ext_val) + .map_err(|e| der_err!(e, "failed to build OctetString"))?, + }; + cert_extensions.push(basic_constraint_ext); // capacity enough + } + + if let Some(attest_extn_val) = attestation_ext { + let attest_ext = Extension { + extn_id: AttestationExtension::OID, + critical: false, + extn_value: OctetString::new(attest_extn_val) + .map_err(|e| der_err!(e, "failed to build OctetString"))?, + }; + cert_extensions.push(attest_ext) // capacity enough + } + + Ok(TbsCertificate { + version: Version::V3, + serial_number: SerialNumber::new(cert_serial) + .map_err(|e| der_err!(e, "failed to build serial number for {:?}", cert_serial))?, + signature: AlgorithmIdentifier { oid: sig_alg_oid, parameters: None }, + issuer: RdnSequence::from_der(cert_issuer) + .map_err(|e| der_err!(e, "failed to build issuer"))?, + validity: x509_cert::time::Validity { + not_before: validity_time_from_datetime(not_before)?, + not_after: validity_time_from_datetime(not_after)?, + }, + subject: RdnSequence::from_der(cert_subject) + .map_err(|e| der_err!(e, "failed to build subject"))?, + subject_public_key_info: spki, + issuer_unique_id: None, + subject_unique_id: None, + extensions: Some(cert_extensions), + }) +} + +/// Extract the Subject field from a `keymint::Certificate` as DER-encoded data. +pub(crate) fn extract_subject(cert: &keymint::Certificate) -> Result, Error> { + let cert = x509_cert::Certificate::from_der(&cert.encoded_certificate) + .map_err(|e| km_err!(EncodingError, "failed to parse certificate: {:?}", e))?; + let subject_data = cert + .tbs_certificate + .subject + .to_der() + .map_err(|e| km_err!(EncodingError, "failed to DER-encode subject: {:?}", e))?; + Ok(subject_data) +} + +/// Construct x.509-cert::time::Time from `DateTime`. +/// RFC 5280 section 4.1.2.5 requires that UtcTime is used up to 2049 +/// and GeneralizedTime from 2050 onwards +fn validity_time_from_datetime(when: DateTime) -> Result { + let dt_err = |_| Error::Der(ErrorKind::DateTime); + let secs_since_epoch: i64 = when.ms_since_epoch / 1000; + + if when.ms_since_epoch >= 0 { + const MAX_UTC_TIME: Duration = Duration::from_secs(2524608000); // 2050-01-01T00:00:00Z + + let duration = Duration::from_secs(u64::try_from(secs_since_epoch).map_err(dt_err)?); + if duration >= MAX_UTC_TIME { + Ok(Time::GeneralTime( + GeneralizedTime::from_unix_duration(duration) + .map_err(|e| der_err!(e, "failed to build GeneralTime for {:?}", when))?, + )) + } else { + Ok(Time::UtcTime( + UtcTime::from_unix_duration(duration) + .map_err(|e| der_err!(e, "failed to build UtcTime for {:?}", when))?, + )) + } + } else { + // TODO: cope with negative offsets from Unix Epoch. + Ok(Time::GeneralTime( + GeneralizedTime::from_unix_duration(Duration::from_secs(0)) + .map_err(|e| der_err!(e, "failed to build GeneralizedTime(0) for {:?}", when))?, + )) + } +} + +pub(crate) fn asn1_der_encode(obj: &T) -> Result, der::Error> { + let mut encoded_data = Vec::::new(); + obj.encode_to_vec(&mut encoded_data)?; + Ok(encoded_data) +} + +/// Build key usage extension bits. +pub(crate) fn key_usage_extension_bits(params: &[KeyParam]) -> KeyUsage { + // Build `KeyUsage` bitmask based on allowed purposes for the key. + let mut key_usage_bits = FlagSet::::default(); + for param in params { + if let KeyParam::Purpose(purpose) = param { + match purpose { + KeyPurpose::Sign | KeyPurpose::Verify => { + key_usage_bits |= KeyUsages::DigitalSignature; + } + KeyPurpose::Decrypt | KeyPurpose::Encrypt => { + key_usage_bits |= KeyUsages::DataEncipherment; + key_usage_bits |= KeyUsages::KeyEncipherment; + } + KeyPurpose::WrapKey => { + key_usage_bits |= KeyUsages::KeyEncipherment; + } + KeyPurpose::AgreeKey => { + key_usage_bits |= KeyUsages::KeyAgreement; + } + KeyPurpose::AttestKey => { + key_usage_bits |= KeyUsages::KeyCertSign; + } + } + } + } + KeyUsage(key_usage_bits) +} + +/// Build basic constraints extension value +pub(crate) fn basic_constraints_ext_value(ca_required: bool) -> BasicConstraints { + BasicConstraints { ca: ca_required, path_len_constraint: None } +} + +/// Attestation extension contents +/// +/// ```asn1 +/// KeyDescription ::= SEQUENCE { +/// attestationVersion INTEGER, +/// attestationSecurityLevel SecurityLevel, +/// keyMintVersion INTEGER, +/// keymintSecurityLevel SecurityLevel, +/// attestationChallenge OCTET_STRING, +/// uniqueId OCTET_STRING, +/// softwareEnforced AuthorizationList, +/// hardwareEnforced AuthorizationList, +/// } +/// ``` +#[derive(Debug, Clone, Sequence, PartialEq)] +pub struct AttestationExtension<'a> { + attestation_version: i32, + attestation_security_level: SecurityLevel, + keymint_version: i32, + keymint_security_level: SecurityLevel, + #[asn1(type = "OCTET STRING")] + attestation_challenge: &'a [u8], + #[asn1(type = "OCTET STRING")] + unique_id: &'a [u8], + sw_enforced: AuthorizationList<'a>, + hw_enforced: AuthorizationList<'a>, +} + +impl AssociatedOid for AttestationExtension<'_> { + const OID: ObjectIdentifier = ATTESTATION_EXTENSION_OID; +} + +/// Security level enumeration +/// ```asn1 +/// SecurityLevel ::= ENUMERATED { +/// Software (0), +/// TrustedEnvironment (1), +/// StrongBox (2), +/// } +/// ``` +#[repr(u32)] +#[derive(Debug, Clone, Copy, Enumerated, PartialEq)] +enum SecurityLevel { + Software = 0, + TrustedEnvironment = 1, + Strongbox = 2, +} + +/// Build an ASN.1 DER-encoded attestation extension. +#[allow(clippy::too_many_arguments)] +pub(crate) fn attestation_extension<'a>( + keymint_version: i32, + challenge: &'a [u8], + app_id: &'a [u8], + security_level: keymint::SecurityLevel, + attestation_ids: Option<&'a crate::AttestationIdInfo>, + params: &'a [KeyParam], + chars: &'a [KeyCharacteristics], + unique_id: &'a Vec, + boot_info: &'a keymint::BootInfo, + additional_attestation_info: &'a [KeyParam], +) -> Result, Error> { + let mut sw_chars: &[KeyParam] = &[]; + let mut hw_chars: &[KeyParam] = &[]; + for characteristic in chars.iter() { + match characteristic.security_level { + keymint::SecurityLevel::Keystore | keymint::SecurityLevel::Software => { + sw_chars = &characteristic.authorizations + } + l if l == security_level => hw_chars = &characteristic.authorizations, + l => { + return Err(km_err!( + InvalidTag, + "found characteristics for unexpected security level {:?}", + l, + )) + } + } + } + let (sw_params, hw_params): (&[KeyParam], &[KeyParam]) = match security_level { + keymint::SecurityLevel::Software => (params, &[]), + _ => (&[], params), + }; + let sw_enforced = AuthorizationList::new( + sw_chars, + sw_params, + attestation_ids, + None, + Some(app_id), + additional_attestation_info, + )?; + let hw_enforced = AuthorizationList::new( + hw_chars, + hw_params, + attestation_ids, + Some(RootOfTrust::from(boot_info)), + None, + &[], + )?; + let sec_level = SecurityLevel::try_from(security_level as u32) + .map_err(|_| km_err!(InvalidArgument, "invalid security level {:?}", security_level))?; + let ext = AttestationExtension { + attestation_version: keymint_version, + attestation_security_level: sec_level, + keymint_version, + keymint_security_level: sec_level, + attestation_challenge: challenge, + unique_id, + sw_enforced, + hw_enforced, + }; + Ok(ext) +} + +/// Struct for creating ASN.1 DER-serialized `AuthorizationList`. The fields in the ASN.1 +/// sequence are categorized into four fields in the struct based on their usage. +/// ```asn1 +/// AuthorizationList ::= SEQUENCE { +/// purpose [1] EXPLICIT SET OF INTEGER OPTIONAL, +/// algorithm [2] EXPLICIT INTEGER OPTIONAL, +/// keySize [3] EXPLICIT INTEGER OPTIONAL, +/// blockMode [4] EXPLICIT SET OF INTEGER OPTIONAL, -- Symmetric keys only +/// digest [5] EXPLICIT SET OF INTEGER OPTIONAL, +/// padding [6] EXPLICIT SET OF INTEGER OPTIONAL, +/// callerNonce [7] EXPLICIT NULL OPTIONAL, -- Symmetric keys only +/// minMacLength [8] EXPLICIT INTEGER OPTIONAL, -- Symmetric keys only +/// ecCurve [10] EXPLICIT INTEGER OPTIONAL, +/// rsaPublicExponent [200] EXPLICIT INTEGER OPTIONAL, +/// mgfDigest [203] EXPLICIT SET OF INTEGER OPTIONAL, +/// rollbackResistance [303] EXPLICIT NULL OPTIONAL, +/// earlyBootOnly [305] EXPLICIT NULL OPTIONAL, +/// activeDateTime [400] EXPLICIT INTEGER OPTIONAL, +/// originationExpireDateTime [401] EXPLICIT INTEGER OPTIONAL, +/// usageExpireDateTime [402] EXPLICIT INTEGER OPTIONAL, +/// usageCountLimit [405] EXPLICIT INTEGER OPTIONAL, +/// userSecureId [502] EXPLICIT INTEGER OPTIONAL, -- Only used on key import +/// noAuthRequired [503] EXPLICIT NULL OPTIONAL, +/// userAuthType [504] EXPLICIT INTEGER OPTIONAL, +/// authTimeout [505] EXPLICIT INTEGER OPTIONAL, +/// allowWhileOnBody [506] EXPLICIT NULL OPTIONAL, +/// trustedUserPresenceReq [507] EXPLICIT NULL OPTIONAL, +/// trustedConfirmationReq [508] EXPLICIT NULL OPTIONAL, +/// unlockedDeviceReq [509] EXPLICIT NULL OPTIONAL, +/// creationDateTime [701] EXPLICIT INTEGER OPTIONAL, +/// origin [702] EXPLICIT INTEGER OPTIONAL, +/// rootOfTrust [704] EXPLICIT RootOfTrust OPTIONAL, +/// osVersion [705] EXPLICIT INTEGER OPTIONAL, +/// osPatchLevel [706] EXPLICIT INTEGER OPTIONAL, +/// attestationApplicationId [709] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdBrand [710] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdDevice [711] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdProduct [712] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdSerial [713] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdImei [714] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdMeid [715] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdManufacturer [716] EXPLICIT OCTET_STRING OPTIONAL, +/// attestationIdModel [717] EXPLICIT OCTET_STRING OPTIONAL, +/// vendorPatchLevel [718] EXPLICIT INTEGER OPTIONAL, +/// bootPatchLevel [719] EXPLICIT INTEGER OPTIONAL, +/// deviceUniqueAttestation [720] EXPLICIT NULL OPTIONAL, +/// attestationIdSecondImei [723] EXPLICIT OCTET_STRING OPTIONAL, +/// -- moduleHash contains a SHA-256 hash of DER-encoded `Modules` +/// moduleHash [724] EXPLICIT OCTET_STRING OPTIONAL, +/// } +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AuthorizationList<'a> { + pub auths: Cow<'a, [KeyParam]>, + pub keygen_params: Cow<'a, [KeyParam]>, + pub rot_info: Option, + pub app_id: Option, + pub additional_attestation_info: Cow<'a, [KeyParam]>, +} + +/// Macro to check that a specified attestation ID matches the provisioned value. +macro_rules! check_attestation_id { + { + $params:expr, $variant:ident, $mustmatch:expr + } => { + { + if let Some(val) = get_opt_tag_value!($params, $variant)? { + match $mustmatch { + None => return Err(km_err!(CannotAttestIds, + "no attestation IDs provisioned")), + Some(want) => if val != want { + return Err(km_err!(CannotAttestIds, + "attestation ID mismatch for {}", + stringify!($variant))) + } + } + } + } + } +} + +impl<'a> AuthorizationList<'a> { + /// Build an `AuthorizationList` ready for serialization. This constructor will fail if device + /// ID attestation is required but the relevant IDs are missing or mismatched. + fn new( + auths: &'a [KeyParam], + keygen_params: &'a [KeyParam], + attestation_ids: Option<&'a crate::AttestationIdInfo>, + rot_info: Option>, + app_id: Option<&'a [u8]>, + additional_attestation_info: &'a [KeyParam], + ) -> Result { + check_attestation_id!(keygen_params, AttestationIdBrand, attestation_ids.map(|v| &v.brand)); + check_attestation_id!( + keygen_params, + AttestationIdDevice, + attestation_ids.map(|v| &v.device) + ); + check_attestation_id!( + keygen_params, + AttestationIdProduct, + attestation_ids.map(|v| &v.product) + ); + check_attestation_id!( + keygen_params, + AttestationIdSerial, + attestation_ids.map(|v| &v.serial) + ); + check_attestation_id!(keygen_params, AttestationIdImei, attestation_ids.map(|v| &v.imei)); + check_attestation_id!( + keygen_params, + AttestationIdSecondImei, + attestation_ids.map(|v| &v.imei2) + ); + check_attestation_id!(keygen_params, AttestationIdMeid, attestation_ids.map(|v| &v.meid)); + check_attestation_id!( + keygen_params, + AttestationIdManufacturer, + attestation_ids.map(|v| &v.manufacturer) + ); + check_attestation_id!(keygen_params, AttestationIdModel, attestation_ids.map(|v| &v.model)); + + let encoded_rot = if let Some(rot) = rot_info { + Some(rot.to_der().map_err(|e| der_err!(e, "failed to encode RoT"))?) + } else { + None + }; + Ok(Self { + auths: auths.into(), + keygen_params: keygen_params.into(), + rot_info: encoded_rot.map(KeyParam::RootOfTrust), + app_id: match app_id { + Some(app_id) => Some(KeyParam::AttestationApplicationId(try_to_vec(app_id)?)), + None => None, + }, + additional_attestation_info: additional_attestation_info.into(), + }) + } + + /// Build an `AuthorizationList` using a set of key parameters. + /// The checks for the attestation ids are not run here in contrast to `AuthorizationList::new` + /// because this method is used to construct an `AuthorizationList` in the decode path rather + /// than in the encode path. Note: decode path is currently used only by + /// `KeyMintTa::import_wrapped_key` functionality, which only uses `auth` field of + /// `AuthorizationList`. Decoding for the whole `AuthorizationList` is added here for the + /// completeness and anticipating a future use case of decoding the attestation extension from + /// an X.509 certificate. + fn new_from_key_params(key_params: Vec) -> Result { + let mut auths = Vec::new(); + let mut keygen_params = Vec::new(); + let mut rot: Option = None; + let mut attest_app_id: Option = None; + let mut additional_attestation_info = Vec::new(); + + // Divide key parameters into key characteristics and key generation parameters. + for param in key_params { + match param { + KeyParam::RootOfTrust(_) => rot = Some(param), + KeyParam::AttestationApplicationId(_) => attest_app_id = Some(param), + KeyParam::AttestationIdBrand(_) + | KeyParam::AttestationIdDevice(_) + | KeyParam::AttestationIdProduct(_) + | KeyParam::AttestationIdSerial(_) + | KeyParam::AttestationIdImei(_) + | KeyParam::AttestationIdSecondImei(_) + | KeyParam::AttestationIdMeid(_) + | KeyParam::AttestationIdManufacturer(_) + | KeyParam::AttestationIdModel(_) => { + keygen_params.try_push(param).map_err(der_alloc_err)? + } + KeyParam::ModuleHash(_) => { + additional_attestation_info.try_push(param).map_err(der_alloc_err)? + } + _ => auths.try_push(param).map_err(der_alloc_err)?, + } + } + Ok(AuthorizationList { + auths: auths.into(), + keygen_params: keygen_params.into(), + rot_info: rot, + app_id: attest_app_id, + additional_attestation_info: additional_attestation_info.into(), + }) + } +} + +/// Convert an error into a `der::Error` indicating allocation failure. +#[inline] +fn der_alloc_err(_e: T) -> der::Error { + der::Error::new(der::ErrorKind::Overlength, der::Length::ZERO) +} + +/// All the fields of AuthorizationList sequence are optional. Therefore, the expected tag and the +/// decoded tag might be different. If they don't match, return the decoded tag to be used in a +/// future call to this method. If the two tags match, continue to read the value, +/// populate key parameters and return None, so that the next call to this method will +/// decode the tag from bytes. See the implementation of [`der::DecodeValue`] trait for +/// AuthorizationList. +fn decode_opt_field<'a, R: der::Reader<'a>>( + decoder: &mut R, + already_read_tag: Option, + expected_tag: keymint::Tag, + key_params: &mut Vec, +) -> Result, der::Error> { + // Decode the tag if no tag is provided + let tag = + if already_read_tag.is_none() { decode_tag_from_bytes(decoder)? } else { already_read_tag }; + match tag { + Some(tag) if tag == expected_tag => { + // Decode the length of the inner encoding + let inner_len = Length::decode(decoder)?; + if decoder.remaining_len() < inner_len { + return Err(der::ErrorKind::Incomplete { + expected_len: inner_len, + actual_len: decoder.remaining_len(), + } + .into()); + } + let next_tlv = decoder.tlv_bytes()?; + decode_value_from_bytes(expected_tag, next_tlv, key_params)?; + Ok(None) + } + Some(tag) => Ok(Some(tag)), // Return the tag for which the value is unread. + None => Ok(None), + } +} + +macro_rules! process_authz_list_tags { + {$decoder:expr, $key_params:expr, ($($tag:ident),*)} => { + let mut non_consumed_tag: Option = None; + ($(non_consumed_tag = decode_opt_field($decoder, + non_consumed_tag, + Tag::$tag, + $key_params)?),*); + if non_consumed_tag.is_some(){ + return Err($decoder.error(der::ErrorKind::Incomplete { + expected_len: Length::ZERO, + actual_len: $decoder.remaining_len(), + })); + } + }; +} + +/// Implementation of [`der::DecodeValue`] which constructs an AuthorizationList from bytes. +impl<'a> der::DecodeValue<'a> for AuthorizationList<'a> { + fn decode_value>(decoder: &mut R, header: der::Header) -> der::Result { + // TODO: define a MAX_SIZE for AuthorizationList and check whether the actual length from + // the length field of header is less than the MAX_SIZE + + // Check for an empty sequence + if header.length.is_zero() { + return Ok(AuthorizationList { + auths: Vec::new().into(), + keygen_params: Vec::new().into(), + rot_info: None, + app_id: None, + additional_attestation_info: Vec::new().into(), + }); + } + if decoder.remaining_len() < header.length { + return Err(der::ErrorKind::Incomplete { + expected_len: header.length, + actual_len: decoder.remaining_len(), + })?; + } + let mut key_params = Vec::new(); + process_authz_list_tags!( + decoder, + &mut key_params, + ( + Purpose, + Algorithm, + KeySize, + BlockMode, + Digest, + Padding, + CallerNonce, + MinMacLength, + EcCurve, + RsaPublicExponent, + RsaOaepMgfDigest, + RollbackResistance, + EarlyBootOnly, + ActiveDatetime, + OriginationExpireDatetime, + UsageExpireDatetime, + UsageCountLimit, + UserSecureId, + NoAuthRequired, + UserAuthType, + AuthTimeout, + AllowWhileOnBody, + TrustedUserPresenceRequired, + TrustedConfirmationRequired, + UnlockedDeviceRequired, + CreationDatetime, + CreationDatetime, + Origin, + RootOfTrust, + OsVersion, + OsPatchlevel, + AttestationApplicationId, + AttestationIdBrand, + AttestationIdDevice, + AttestationIdProduct, + AttestationIdSerial, + AttestationIdSerial, + AttestationIdSerial, + AttestationIdImei, + AttestationIdMeid, + AttestationIdManufacturer, + AttestationIdModel, + VendorPatchlevel, + BootPatchlevel, + DeviceUniqueAttestation, + AttestationIdSecondImei, + ModuleHash + ) + ); + + // Process the key params and construct the `AuthorizationList` + AuthorizationList::new_from_key_params(key_params) + } +} + +// Macros to decode key parameters from their ASN.1 encoding in one of the forms: +// field [] EXPLICIT SET OF INTEGER OPTIONAL +// field [] EXPLICIT INTEGER OPTIONAL +// field [] EXPLICIT NULL OPTIONAL +// field [] EXPLICIT OCTET STRING OPTIONAL +// There are three different variants for the INTEGER type. + +macro_rules! key_params_from_asn1_set_of_integer { + {$variant:ident, $tlv_bytes:expr, $key_params:expr} => { + let vals = SetOfVec::::from_der($tlv_bytes)?; + for val in vals.into_vec() { + $key_params.try_push(KeyParam::$variant(val.try_into().map_err( + |_e| der::ErrorKind::Value {tag: der::Tag::Set})?)).map_err(der_alloc_err)?; + } + } +} + +macro_rules! key_param_from_asn1_integer { + {$variant:ident, $int_type:ident, $tlv_bytes:expr, $key_params:expr} => { + let val = $int_type::from_der($tlv_bytes)?; + $key_params.try_push(KeyParam::$variant(val.try_into().map_err( + |_e| der::ErrorKind::Value {tag: der::Tag::Integer})?)).map_err(der_alloc_err)?; + } +} + +macro_rules! key_param_from_asn1_integer_newtype { + {$variant:ident, $int_type:ident, $newtype:ident, $tlv_bytes:expr, $key_params:expr} => { + let val = $int_type::from_der($tlv_bytes)?; + $key_params.try_push(KeyParam::$variant($newtype(val.try_into().map_err( + |_e| der::ErrorKind::Value {tag: der::Tag::Integer})?))).map_err(der_alloc_err)?; + } +} + +macro_rules! key_param_from_asn1_null { + {$variant:ident, $tlv_bytes:expr, $key_params:expr} => { + Null::from_der($tlv_bytes)?; + $key_params.try_push(KeyParam::$variant).map_err(der_alloc_err)?; + }; +} + +macro_rules! key_param_from_asn1_integer_datetime { + {$variant:ident, $tlv_bytes:expr, $key_params:expr} => { + let val = i64::from_der($tlv_bytes)?; + $key_params + .try_push(KeyParam::$variant(DateTime{ms_since_epoch: val})) + .map_err(der_alloc_err)?; + }; +} + +macro_rules! key_param_from_asn1_octet_string { + {$variant:ident, $tlv_bytes:expr, $key_params:expr} => { + let val = OctetStringRef::from_der($tlv_bytes)?; + $key_params.try_push(KeyParam::$variant(try_to_vec(val.as_bytes()) + .map_err(der_alloc_err)?)).map_err(der_alloc_err)?; + }; +} + +fn decode_value_from_bytes( + tag: keymint::Tag, + tlv_bytes: &[u8], + key_params: &mut Vec, +) -> Result<(), der::Error> { + match tag { + Tag::Purpose => { + key_params_from_asn1_set_of_integer!(Purpose, tlv_bytes, key_params); + } + Tag::Algorithm => { + key_param_from_asn1_integer!(Algorithm, i32, tlv_bytes, key_params); + } + Tag::KeySize => { + key_param_from_asn1_integer_newtype!( + KeySize, + u32, + KeySizeInBits, + tlv_bytes, + key_params + ); + } + Tag::BlockMode => { + key_params_from_asn1_set_of_integer!(BlockMode, tlv_bytes, key_params); + } + Tag::Digest => { + key_params_from_asn1_set_of_integer!(Digest, tlv_bytes, key_params); + } + Tag::Padding => { + key_params_from_asn1_set_of_integer!(Padding, tlv_bytes, key_params); + } + Tag::CallerNonce => { + key_param_from_asn1_null!(CallerNonce, tlv_bytes, key_params); + } + Tag::MinMacLength => { + key_param_from_asn1_integer!(MinMacLength, u32, tlv_bytes, key_params); + } + Tag::EcCurve => { + key_param_from_asn1_integer!(EcCurve, i32, tlv_bytes, key_params); + } + Tag::RsaPublicExponent => { + key_param_from_asn1_integer_newtype!( + RsaPublicExponent, + u64, + RsaExponent, + tlv_bytes, + key_params + ); + } + Tag::RsaOaepMgfDigest => { + key_params_from_asn1_set_of_integer!(RsaOaepMgfDigest, tlv_bytes, key_params); + } + Tag::RollbackResistance => { + key_param_from_asn1_null!(RollbackResistance, tlv_bytes, key_params); + } + Tag::EarlyBootOnly => { + key_param_from_asn1_null!(EarlyBootOnly, tlv_bytes, key_params); + } + Tag::ActiveDatetime => { + key_param_from_asn1_integer_datetime!(ActiveDatetime, tlv_bytes, key_params); + } + Tag::OriginationExpireDatetime => { + key_param_from_asn1_integer_datetime!(OriginationExpireDatetime, tlv_bytes, key_params); + } + Tag::UsageExpireDatetime => { + key_param_from_asn1_integer_datetime!(UsageExpireDatetime, tlv_bytes, key_params); + } + Tag::UsageCountLimit => { + key_param_from_asn1_integer!(UsageCountLimit, u32, tlv_bytes, key_params); + } + Tag::UserSecureId => { + // Note that the `UserSecureId` tag has tag type `ULONG_REP` indicating that it can be + // repeated, but the ASN.1 schema for `AuthorizationList` has this field as having type + // `INTEGER` not `SET OF INTEGER`. This reflects the special usage of `UserSecureId` + // in `importWrappedKey()` processing. + key_param_from_asn1_integer!(UserSecureId, u64, tlv_bytes, key_params); + } + Tag::NoAuthRequired => { + key_param_from_asn1_null!(NoAuthRequired, tlv_bytes, key_params); + } + Tag::UserAuthType => { + key_param_from_asn1_integer!(UserAuthType, u32, tlv_bytes, key_params); + } + Tag::AuthTimeout => { + key_param_from_asn1_integer!(AuthTimeout, u32, tlv_bytes, key_params); + } + Tag::AllowWhileOnBody => { + key_param_from_asn1_null!(AllowWhileOnBody, tlv_bytes, key_params); + } + Tag::TrustedUserPresenceRequired => { + key_param_from_asn1_null!(TrustedUserPresenceRequired, tlv_bytes, key_params); + } + Tag::TrustedConfirmationRequired => { + key_param_from_asn1_null!(TrustedConfirmationRequired, tlv_bytes, key_params); + } + Tag::UnlockedDeviceRequired => { + key_param_from_asn1_null!(UnlockedDeviceRequired, tlv_bytes, key_params); + } + Tag::CreationDatetime => { + key_param_from_asn1_integer_datetime!(CreationDatetime, tlv_bytes, key_params); + } + Tag::Origin => { + key_param_from_asn1_integer!(Origin, i32, tlv_bytes, key_params); + } + Tag::RootOfTrust => { + key_params + .try_push(KeyParam::RootOfTrust(try_to_vec(tlv_bytes).map_err(der_alloc_err)?)) + .map_err(der_alloc_err)?; + } + Tag::OsVersion => { + key_param_from_asn1_integer!(OsVersion, u32, tlv_bytes, key_params); + } + Tag::OsPatchlevel => { + key_param_from_asn1_integer!(OsPatchlevel, u32, tlv_bytes, key_params); + } + Tag::AttestationApplicationId => { + key_param_from_asn1_octet_string!(AttestationApplicationId, tlv_bytes, key_params); + } + Tag::AttestationIdBrand => { + key_param_from_asn1_octet_string!(AttestationIdBrand, tlv_bytes, key_params); + } + Tag::AttestationIdDevice => { + key_param_from_asn1_octet_string!(AttestationIdDevice, tlv_bytes, key_params); + } + Tag::AttestationIdProduct => { + key_param_from_asn1_octet_string!(AttestationIdProduct, tlv_bytes, key_params); + } + Tag::AttestationIdSerial => { + key_param_from_asn1_octet_string!(AttestationIdSerial, tlv_bytes, key_params); + } + Tag::AttestationIdImei => { + key_param_from_asn1_octet_string!(AttestationIdImei, tlv_bytes, key_params); + } + Tag::AttestationIdSecondImei => { + key_param_from_asn1_octet_string!(AttestationIdSecondImei, tlv_bytes, key_params); + } + Tag::AttestationIdMeid => { + key_param_from_asn1_octet_string!(AttestationIdMeid, tlv_bytes, key_params); + } + Tag::AttestationIdManufacturer => { + key_param_from_asn1_octet_string!(AttestationIdManufacturer, tlv_bytes, key_params); + } + Tag::AttestationIdModel => { + key_param_from_asn1_octet_string!(AttestationIdModel, tlv_bytes, key_params); + } + Tag::VendorPatchlevel => { + key_param_from_asn1_integer!(VendorPatchlevel, u32, tlv_bytes, key_params); + } + Tag::BootPatchlevel => { + key_param_from_asn1_integer!(BootPatchlevel, u32, tlv_bytes, key_params); + } + Tag::DeviceUniqueAttestation => { + key_param_from_asn1_null!(DeviceUniqueAttestation, tlv_bytes, key_params); + } + Tag::ModuleHash => { + key_param_from_asn1_octet_string!(ModuleHash, tlv_bytes, key_params); + } + _ => { + // Note: `der::Error` or `der::ErrorKind` is not expressive enough for decoding + // tags in high tag form. Documentation of this error kind does not match this + // situation. But we use the `der::ErrorKind` as close as possible. + return Err(der::ErrorKind::TagNumberInvalid.into()); + } + } + Ok(()) +} + +/// Decode the tag of a field in AuthorizationList. +fn decode_tag_from_bytes<'a, R: der::Reader<'a>>( + decoder: &mut R, +) -> Result, der::Error> { + // Avoid reading for tags beyond the size of the encoded AuthorizationList + if decoder.remaining_len() == Length::ZERO { + return Ok(None); + } + let b1 = decoder.read_byte()?; + let raw_tag = if b1 & 0xbfu8 == 0xbfu8 { + // High tag form, read the next byte + let b2 = decoder.read_byte()?; + if b2 & 0x80u8 == 0x80u8 { + // Encoded tag length is 3, read the next byte + let b3 = decoder.read_byte()?; + let tag_byte: u16 = ((b2 ^ 0x80u8) as u16) << 7; + (tag_byte | b3 as u16) as u32 + } else { + b2 as u32 + } + } else { + (b1 ^ 0b10100000u8) as u32 + }; + let tag = from_raw_tag_value(raw_tag); + if tag == Tag::Invalid { + // Note: der::Error or der::ErrorKind is not expressive enough for decoding tags + // in high tag form. Documentation of this error kind does not match this situation. + // Find a better way to express the error. + Err(der::ErrorKind::TagNumberInvalid.into()) + } else { + Ok(Some(tag)) + } +} + +// Macros to extract key characteristics for ASN.1 encoding into one of the forms: +// field [] EXPLICIT SET OF INTEGER OPTIONAL +// field [] EXPLICIT INTEGER OPTIONAL +// field [] EXPLICIT NULL OPTIONAL +// field [] EXPLICIT OCTET STRING OPTIONAL +// together with an extra variant that deals with OCTET STRING values that must match +// a provisioned attestation ID value. +macro_rules! asn1_set_of_integer { + { $params:expr, $variant:ident } => { + { + let mut results = Vec::new(); + for param in $params.as_ref() { + if let KeyParam::$variant(v) = param { + results.try_push(v.clone()).map_err(der_alloc_err)?; + } + } + if !results.is_empty() { + // The input key characteristics have been sorted and so are in numerical order, but + // may contain duplicates that need to be weeded out. + let mut set = der::asn1::SetOfVec::new(); + let mut prev_val = None; + for val in results { + let val = val as i64; + if let Some(prev) = prev_val { + if prev == val { + continue; // skip duplicate + } + } + set.insert_ordered(val)?; + prev_val = Some(val); + } + Some(ExplicitTaggedValue { + tag: raw_tag_value(Tag::$variant), + val: set, + }) + } else { + None + } + } + } +} +macro_rules! asn1_integer { + { $params:expr, $variant:ident } => { + if let Some(val) = get_opt_tag_value!($params.as_ref(), $variant).map_err(|_e| { + log::warn!("failed to get {} value for ext", stringify!($variant)); + der::Error::new(der::ErrorKind::Failed, der::Length::ZERO) + })? { + Some(ExplicitTaggedValue { + tag: raw_tag_value(Tag::$variant), + val: *val as i64 + }) + } else { + None + } + } +} +macro_rules! asn1_integer_newtype { + { $params:expr, $variant:ident } => { + if let Some(val) = get_opt_tag_value!($params.as_ref(), $variant).map_err(|_e| { + log::warn!("failed to get {} value for ext", stringify!($variant)); + der::Error::new(der::ErrorKind::Failed, der::Length::ZERO) + })? { + Some(ExplicitTaggedValue { + tag: raw_tag_value(Tag::$variant), + val: val.0 as i64 + }) + } else { + None + } + } +} +macro_rules! asn1_integer_datetime { + { $params:expr, $variant:ident } => { + if let Some(val) = get_opt_tag_value!($params.as_ref(), $variant).map_err(|_e| { + log::warn!("failed to get {} value for ext", stringify!($variant)); + der::Error::new(der::ErrorKind::Failed, der::Length::ZERO) + })? { + Some(ExplicitTaggedValue { + tag: raw_tag_value(Tag::$variant), + val: val.ms_since_epoch + }) + } else { + None + } + } +} +macro_rules! asn1_null { + { $params:expr, $variant:ident } => { + if get_bool_tag_value!($params.as_ref(), $variant).map_err(|_e| { + log::warn!("failed to get {} value for ext", stringify!($variant)); + der::Error::new(der::ErrorKind::Failed, der::Length::ZERO) + })? { + Some(ExplicitTaggedValue { + tag: raw_tag_value(Tag::$variant), + val: () + }) + } else { + None + } + } +} +macro_rules! asn1_octet_string { + { $params:expr, $variant:ident } => { + if let Some(val) = get_opt_tag_value!($params.as_ref(), $variant).map_err(|_e| { + log::warn!("failed to get {} value for ext", stringify!($variant)); + der::Error::new(der::ErrorKind::Failed, der::Length::ZERO) + })? { + Some(ExplicitTaggedValue { + tag: raw_tag_value(Tag::$variant), + val: der::asn1::OctetStringRef::new(val)?, + }) + } else { + None + } + } +} + +fn asn1_val( + val: Option>, + writer: &mut impl der::Writer, +) -> der::Result<()> { + if let Some(val) = val { + val.encode(writer) + } else { + Ok(()) + } +} + +fn asn1_len(val: Option>) -> der::Result { + match val { + Some(val) => val.encoded_len(), + None => Ok(Length::ZERO), + } +} + +impl<'a> Sequence<'a> for AuthorizationList<'a> {} + +impl EncodeValue for AuthorizationList<'_> { + fn value_len(&self) -> der::Result { + let mut length = asn1_len(asn1_set_of_integer!(self.auths, Purpose))? + + asn1_len(asn1_integer!(self.auths, Algorithm))? + + asn1_len(asn1_integer_newtype!(self.auths, KeySize))? + + asn1_len(asn1_set_of_integer!(self.auths, BlockMode))? + + asn1_len(asn1_set_of_integer!(self.auths, Digest))? + + asn1_len(asn1_set_of_integer!(self.auths, Padding))? + + asn1_len(asn1_null!(self.auths, CallerNonce))? + + asn1_len(asn1_integer!(self.auths, MinMacLength))? + + asn1_len(asn1_integer!(self.auths, EcCurve))? + + asn1_len(asn1_integer_newtype!(self.auths, RsaPublicExponent))? + + asn1_len(asn1_set_of_integer!(self.auths, RsaOaepMgfDigest))? + + asn1_len(asn1_null!(self.auths, RollbackResistance))? + + asn1_len(asn1_null!(self.auths, EarlyBootOnly))? + + asn1_len(asn1_integer_datetime!(self.auths, ActiveDatetime))? + + asn1_len(asn1_integer_datetime!(self.auths, OriginationExpireDatetime))? + + asn1_len(asn1_integer_datetime!(self.auths, UsageExpireDatetime))? + + asn1_len(asn1_integer!(self.auths, UsageCountLimit))? + + asn1_len(asn1_null!(self.auths, NoAuthRequired))? + + asn1_len(asn1_integer!(self.auths, UserAuthType))? + + asn1_len(asn1_integer!(self.auths, AuthTimeout))? + + asn1_len(asn1_null!(self.auths, AllowWhileOnBody))? + + asn1_len(asn1_null!(self.auths, TrustedUserPresenceRequired))? + + asn1_len(asn1_null!(self.auths, TrustedConfirmationRequired))? + + asn1_len(asn1_null!(self.auths, UnlockedDeviceRequired))? + + asn1_len(asn1_integer_datetime!(self.auths, CreationDatetime))? + + asn1_len(asn1_integer!(self.auths, Origin))?; + if let Some(KeyParam::RootOfTrust(encoded_rot_info)) = &self.rot_info { + length = length + + ExplicitTaggedValue { + tag: raw_tag_value(Tag::RootOfTrust), + val: RootOfTrust::from_der(encoded_rot_info.as_slice())?, + } + .encoded_len()?; + } + length = length + + asn1_len(asn1_integer!(self.auths, OsVersion))? + + asn1_len(asn1_integer!(self.auths, OsPatchlevel))?; + if let Some(KeyParam::AttestationApplicationId(app_id)) = &self.app_id { + length = length + + ExplicitTaggedValue { + tag: raw_tag_value(Tag::AttestationApplicationId), + val: der::asn1::OctetStringRef::new(app_id.as_slice())?, + } + .encoded_len()?; + } + length = length + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdBrand))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdDevice))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdProduct))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdSerial))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdImei))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdMeid))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdManufacturer))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdModel))? + + asn1_len(asn1_integer!(self.auths, VendorPatchlevel))? + + asn1_len(asn1_integer!(self.auths, BootPatchlevel))? + + asn1_len(asn1_null!(self.auths, DeviceUniqueAttestation))? + + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdSecondImei))? + + asn1_len(asn1_octet_string!(&self.additional_attestation_info, ModuleHash))?; + length + } + + fn encode_value(&self, writer: &mut impl der::Writer) -> der::Result<()> { + asn1_val(asn1_set_of_integer!(self.auths, Purpose), writer)?; + asn1_val(asn1_integer!(self.auths, Algorithm), writer)?; + asn1_val(asn1_integer_newtype!(self.auths, KeySize), writer)?; + asn1_val(asn1_set_of_integer!(self.auths, BlockMode), writer)?; + asn1_val(asn1_set_of_integer!(self.auths, Digest), writer)?; + asn1_val(asn1_set_of_integer!(self.auths, Padding), writer)?; + asn1_val(asn1_null!(self.auths, CallerNonce), writer)?; + asn1_val(asn1_integer!(self.auths, MinMacLength), writer)?; + asn1_val(asn1_integer!(self.auths, EcCurve), writer)?; + asn1_val(asn1_integer_newtype!(self.auths, RsaPublicExponent), writer)?; + asn1_val(asn1_set_of_integer!(self.auths, RsaOaepMgfDigest), writer)?; + asn1_val(asn1_null!(self.auths, RollbackResistance), writer)?; + asn1_val(asn1_null!(self.auths, EarlyBootOnly), writer)?; + asn1_val(asn1_integer_datetime!(self.auths, ActiveDatetime), writer)?; + asn1_val(asn1_integer_datetime!(self.auths, OriginationExpireDatetime), writer)?; + asn1_val(asn1_integer_datetime!(self.auths, UsageExpireDatetime), writer)?; + asn1_val(asn1_integer!(self.auths, UsageCountLimit), writer)?; + // Skip `UserSecureId` as it's only included in the extension for + // importWrappedKey() cases. + asn1_val(asn1_null!(self.auths, NoAuthRequired), writer)?; + asn1_val(asn1_integer!(self.auths, UserAuthType), writer)?; + asn1_val(asn1_integer!(self.auths, AuthTimeout), writer)?; + asn1_val(asn1_null!(self.auths, AllowWhileOnBody), writer)?; + asn1_val(asn1_null!(self.auths, TrustedUserPresenceRequired), writer)?; + asn1_val(asn1_null!(self.auths, TrustedConfirmationRequired), writer)?; + asn1_val(asn1_null!(self.auths, UnlockedDeviceRequired), writer)?; + asn1_val(asn1_integer_datetime!(self.auths, CreationDatetime), writer)?; + asn1_val(asn1_integer!(self.auths, Origin), writer)?; + // Root of trust info is a special case (not in key characteristics). + if let Some(KeyParam::RootOfTrust(encoded_rot_info)) = &self.rot_info { + ExplicitTaggedValue { + tag: raw_tag_value(Tag::RootOfTrust), + val: RootOfTrust::from_der(encoded_rot_info.as_slice())?, + } + .encode(writer)?; + } + asn1_val(asn1_integer!(self.auths, OsVersion), writer)?; + asn1_val(asn1_integer!(self.auths, OsPatchlevel), writer)?; + // Attestation application ID is a special case (not in key characteristics). + if let Some(KeyParam::AttestationApplicationId(app_id)) = &self.app_id { + ExplicitTaggedValue { + tag: raw_tag_value(Tag::AttestationApplicationId), + val: der::asn1::OctetStringRef::new(app_id.as_slice())?, + } + .encode(writer)?; + } + // Accuracy of attestation IDs has already been checked, so just copy across. + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdBrand), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdDevice), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdProduct), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdSerial), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdImei), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdMeid), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdManufacturer), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdModel), writer)?; + asn1_val(asn1_integer!(self.auths, VendorPatchlevel), writer)?; + asn1_val(asn1_integer!(self.auths, BootPatchlevel), writer)?; + asn1_val(asn1_null!(self.auths, DeviceUniqueAttestation), writer)?; + asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdSecondImei), writer)?; + asn1_val(asn1_octet_string!(&self.additional_attestation_info, ModuleHash), writer)?; + Ok(()) + } +} + +struct ExplicitTaggedValue { + pub tag: u32, + pub val: T, +} + +impl ExplicitTaggedValue { + fn explicit_tag_len(&self) -> der::Result { + match self.tag { + 0..=0x1e => Ok(der::Length::ONE), + 0x1f..=0x7f => Ok(der::Length::new(2)), + 0x80..=0x3fff => Ok(der::Length::new(3)), + _ => Err(der::ErrorKind::Overflow.into()), + } + } + + fn explicit_tag_encode(&self, encoder: &mut dyn der::Writer) -> der::Result<()> { + match self.tag { + 0..=0x1e => { + // b101vvvvv is context-specific+constructed + encoder.write_byte(0b10100000u8 | (self.tag as u8)) + } + 0x1f..=0x7f => { + // b101 11111 indicates a context-specific+constructed long-form tag number + encoder.write_byte(0b10111111)?; + encoder.write_byte(self.tag as u8) + } + 0x80..=0x3fff => { + // b101 11111 indicates a context-specific+constructed long-form tag number + encoder.write_byte(0b10111111)?; + encoder.write_byte((self.tag >> 7) as u8 | 0x80u8)?; + encoder.write_byte((self.tag & 0x007f) as u8) + } + _ => Err(der::ErrorKind::Overflow.into()), + } + } +} + +/// The der library explicitly does not support `TagNumber` values bigger than 31, +/// which are required here. Work around this by manually providing the encoding functionality. +impl Encode for ExplicitTaggedValue { + fn encoded_len(&self) -> der::Result { + let inner_len = self.val.encoded_len()?; + self.explicit_tag_len() + inner_len.encoded_len()? + inner_len + } + + fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> { + let inner_len = self.val.encoded_len()?; + self.explicit_tag_encode(encoder)?; + inner_len.encode(encoder)?; + self.val.encode(encoder) + } +} + +/// Root of Trust ASN.1 structure +/// ```asn1 +/// * RootOfTrust ::= SEQUENCE { +/// * verifiedBootKey OCTET_STRING, +/// * deviceLocked BOOLEAN, +/// * verifiedBootState VerifiedBootState, +/// * verifiedBootHash OCTET_STRING, +/// * } +/// ``` +#[derive(Debug, Clone, Sequence)] +struct RootOfTrust<'a> { + #[asn1(type = "OCTET STRING")] + verified_boot_key: &'a [u8], + device_locked: bool, + verified_boot_state: VerifiedBootState, + #[asn1(type = "OCTET STRING")] + verified_boot_hash: &'a [u8], +} + +impl<'a> From<&'a keymint::BootInfo> for RootOfTrust<'a> { + fn from(info: &keymint::BootInfo) -> RootOfTrust { + let verified_boot_key: &[u8] = if info.verified_boot_key.is_empty() { + // If an empty verified boot key was passed by the boot loader, set the verified boot + // key in the attestation to all zeroes. + &EMPTY_BOOT_KEY[..] + } else { + &info.verified_boot_key[..] + }; + RootOfTrust { + verified_boot_key, + device_locked: info.device_boot_locked, + verified_boot_state: info.verified_boot_state.into(), + verified_boot_hash: &info.verified_boot_hash[..], + } + } +} + +/// Verified Boot State as ASN.1 ENUMERATED type. +///```asn1 +/// * VerifiedBootState ::= ENUMERATED { +/// * Verified (0), +/// * SelfSigned (1), +/// * Unverified (2), +/// * Failed (3), +/// * } +///``` +#[repr(u32)] +#[derive(Debug, Clone, Copy, Enumerated)] +enum VerifiedBootState { + Verified = 0, + SelfSigned = 1, + Unverified = 2, + Failed = 3, +} + +impl From for VerifiedBootState { + fn from(state: keymint::VerifiedBootState) -> VerifiedBootState { + match state { + keymint::VerifiedBootState::Verified => VerifiedBootState::Verified, + keymint::VerifiedBootState::SelfSigned => VerifiedBootState::SelfSigned, + keymint::VerifiedBootState::Unverified => VerifiedBootState::Unverified, + keymint::VerifiedBootState::Failed => VerifiedBootState::Failed, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::KeyMintHalVersion; + + #[test] + fn test_attest_ext_encode_decode() { + let sec_level = SecurityLevel::TrustedEnvironment; + let ext = AttestationExtension { + attestation_version: KeyMintHalVersion::V3 as i32, + attestation_security_level: sec_level, + keymint_version: KeyMintHalVersion::V3 as i32, + keymint_security_level: sec_level, + attestation_challenge: b"abc", + unique_id: b"xxx", + sw_enforced: AuthorizationList::new(&[], &[], None, None, None, &[]).unwrap(), + hw_enforced: AuthorizationList::new( + &[KeyParam::Algorithm(keymint::Algorithm::Ec)], + &[], + None, + Some(RootOfTrust { + verified_boot_key: &[0xbbu8; 32], + device_locked: false, + verified_boot_state: VerifiedBootState::Unverified, + verified_boot_hash: &[0xee; 32], + }), + None, + &[], + ) + .unwrap(), + }; + let got = ext.to_der().unwrap(); + let want = concat!( + "3071", // SEQUENCE + "0202", // INTEGER len 2 + "012c", // 300 + "0a01", // ENUM len 1 + "01", // 1 (TrustedEnvironment) + "0202", // INTEGER len 2 + "012c", // 300 + "0a01", // ENUM len 1 + "01", // 1 (TrustedEnvironement) + "0403", // BYTE STRING len 3 + "616263", // b"abc" + "0403", // BYTE STRING len 3 + "787878", // b"xxx" + "3000", // SEQUENCE len 0 + "3055", // SEQUENCE len 55 + "a203", // EXPLICIT [2] + "0201", // INTEGER len 1 + "03", // 3 (Algorithm::Ec) + "bf8540", + "4c", // EXPLICIT [704] len 0x4c + "304a", // SEQUENCE len x4a + "0420", // OCTET STRING len 32 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "0101", // BOOLEAN len 1 + "00", // false + "0a01", // ENUMERATED len 1 + "02", // Unverified(2) + "0420", // OCTET STRING len 32 + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + ); + assert_eq!(hex::encode(&got), want); + assert_eq!(AttestationExtension::from_der(&got).unwrap(), ext); + } + + #[test] + fn test_explicit_tagged_value() { + assert_eq!( + hex::encode(ExplicitTaggedValue { tag: 2, val: 16 }.to_der().unwrap()), + "a203020110" + ); + assert_eq!( + hex::encode(ExplicitTaggedValue { tag: 2, val: () }.to_der().unwrap()), + "a2020500" + ); + assert_eq!( + hex::encode(ExplicitTaggedValue { tag: 503, val: 16 }.to_der().unwrap()), + "bf837703020110" + ); + } + + #[test] + fn test_authz_list_encode_decode() { + let additional_attestation_info = [KeyParam::ModuleHash(vec![0xaa; 32])]; + let authz_list = AuthorizationList::new( + &[KeyParam::Algorithm(keymint::Algorithm::Ec)], + &[], + None, + Some(RootOfTrust { + verified_boot_key: &[0xbbu8; 32], + device_locked: false, + verified_boot_state: VerifiedBootState::Unverified, + verified_boot_hash: &[0xee; 32], + }), + None, + &additional_attestation_info, + ) + .unwrap(); + let got = authz_list.to_der().unwrap(); + let want: &str = concat!( + "307b", // SEQUENCE len 123 + "a203", // EXPLICIT [2] + "0201", // INTEGER len 1 + "03", // 3 (Algorithm::Ec) + "bf8540", + "4c", // EXPLICIT [704] len 0x4c + "304a", // SEQUENCE len x4a + "0420", // OCTET STRING len 32 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "0101", // BOOLEAN len 1 + "00", // false + "0a01", // ENUMERATED len 1 + "02", // Unverified(2) + "0420", // OCTET STRING len 32 + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "bf8554", + "22", // EXPLICIT [724] len 34 + "0420", // OCTET STRING len 32 + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ); + // encode + assert_eq!(hex::encode(&got), want); + // decode from encoded + assert_eq!(AuthorizationList::from_der(got.as_slice()).unwrap(), authz_list); + } + + #[test] + fn test_authz_list_user_secure_id_encode() { + // Create an authorization list that includes multiple values for SecureUserId. + let authz_list = AuthorizationList::new( + &[ + KeyParam::Algorithm(keymint::Algorithm::Ec), + KeyParam::UserSecureId(42), + KeyParam::UserSecureId(43), + KeyParam::UserSecureId(44), + ], + &[], + None, + Some(RootOfTrust { + verified_boot_key: &[0xbbu8; 32], + device_locked: false, + verified_boot_state: VerifiedBootState::Unverified, + verified_boot_hash: &[0xee; 32], + }), + None, + &[], + ) + .unwrap(); + let got = authz_list.to_der().unwrap(); + // The `SecureUserId` values are *not* included in the generated output. + let want: &str = concat!( + "3055", // SEQUENCE len 55 + "a203", // EXPLICIT [2] + "0201", // INTEGER len 1 + "03", // 3 (Algorithm::Ec) + "bf8540", + "4c", // EXPLICIT [704] len 0x4c + "304a", // SEQUENCE len x4a + "0420", // OCTET STRING len 32 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "0101", // BOOLEAN len 1 + "00", // false + "0a01", // ENUMERATED len 1 + "02", // Unverified(2) + "0420", // OCTET STRING len 32 + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + ); + // encode + assert_eq!(hex::encode(got), want); + } + + #[test] + fn test_authz_list_user_secure_id_decode() { + // Create a DER-encoded `AuthorizationList` that includes a `UserSecureId` value. + let input = hex::decode(concat!( + "305c", // SEQUENCE + "a203", // EXPLICIT [2] len 3 + "0201", // INTEGER len 1 + "03", // 3 (Algorithm::Ec) + "bf8376", // EXPLICIT [502] + "03", // len 3 + "0201", // INTEGER len 1 + "02", // 2 + "bf8540", // EXPLICIT [704] + "4c", // len 0x4c + "304a", // SEQUENCE len x4a + "0420", // OCTET STRING len 32 + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "0101", // BOOLEAN len 1 + "00", // false + "0a01", // ENUMERATED len 1 + "02", // Unverified(2) + "0420", // OCTET STRING len 32 + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + )) + .unwrap(); + let got = AuthorizationList::from_der(&input).unwrap(); + + let want = AuthorizationList::new( + &[KeyParam::Algorithm(keymint::Algorithm::Ec), KeyParam::UserSecureId(2)], + &[], + None, + Some(RootOfTrust { + verified_boot_key: &[0xbbu8; 32], + device_locked: false, + verified_boot_state: VerifiedBootState::Unverified, + verified_boot_hash: &[0xee; 32], + }), + None, + &[], + ) + .unwrap(); + + assert_eq!(got, want); + } + + #[test] + fn test_authz_list_dup_encode() { + use kmr_wire::keymint::Digest; + let authz_list = AuthorizationList::new( + &[ + KeyParam::Digest(Digest::None), + KeyParam::Digest(Digest::Sha1), + KeyParam::Digest(Digest::Sha1), // duplicate value + ], + &[], + None, + Some(RootOfTrust { + verified_boot_key: &[0xbbu8; 32], + device_locked: false, + verified_boot_state: VerifiedBootState::Unverified, + verified_boot_hash: &[0xee; 32], + }), + None, + &[], + ) + .unwrap(); + let got = authz_list.to_der().unwrap(); + assert!(AuthorizationList::from_der(got.as_slice()).is_ok()); + } + + #[test] + fn test_authz_list_order_fail() { + use kmr_wire::keymint::Digest; + let authz_list = AuthorizationList::new( + &[KeyParam::Digest(Digest::Sha1), KeyParam::Digest(Digest::None)], + &[], + None, + Some(RootOfTrust { + verified_boot_key: &[0xbbu8; 32], + device_locked: false, + verified_boot_state: VerifiedBootState::Unverified, + verified_boot_hash: &[0xee; 32], + }), + None, + &[], + ) + .unwrap(); + assert!(authz_list.to_der().is_err()); + } +} diff --git a/libs/rust/ta/src/clock.rs b/libs/rust/ta/src/clock.rs new file mode 100644 index 0000000..f0da98c --- /dev/null +++ b/libs/rust/ta/src/clock.rs @@ -0,0 +1,49 @@ +// Copyright 2022, 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. + +//! TA functionality for secure clocks. + +use Vec; +use core::mem::size_of; +use kmr_common::{km_err, vec_try_with_capacity, Error}; +use kmr_wire::secureclock::{TimeStampToken, TIME_STAMP_MAC_LABEL}; + +impl crate::KeyMintTa { + pub(crate) fn generate_timestamp(&self, challenge: i64) -> Result { + if let Some(clock) = &self.imp.clock { + let mut ret = + TimeStampToken { challenge, timestamp: clock.now().into(), mac: Vec::new() }; + let mac_input = self.dev.keys.timestamp_token_mac_input(&ret)?; + ret.mac = self.device_hmac(&mac_input)?; + Ok(ret) + } else { + Err(km_err!(Unimplemented, "no clock available")) + } + } +} + +/// Build the HMAC input for a [`TimeStampToken`] +pub fn timestamp_token_mac_input(token: &TimeStampToken) -> Result, Error> { + let mut result = vec_try_with_capacity!( + TIME_STAMP_MAC_LABEL.len() + + size_of::() + // challenge (BE) + size_of::() + // timestamp (BE) + size_of::() // 1u32 (BE) + )?; + result.extend_from_slice(TIME_STAMP_MAC_LABEL); + result.extend_from_slice(&token.challenge.to_be_bytes()[..]); + result.extend_from_slice(&token.timestamp.milliseconds.to_be_bytes()[..]); + result.extend_from_slice(&1u32.to_be_bytes()[..]); + Ok(result) +} diff --git a/libs/rust/ta/src/device.rs b/libs/rust/ta/src/device.rs new file mode 100644 index 0000000..4b55b11 --- /dev/null +++ b/libs/rust/ta/src/device.rs @@ -0,0 +1,398 @@ +// Copyright 2022, 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. + +//! Traits representing access to device-specific information and functionality. + +use crate::coset::{iana, AsCborValue, CoseSign1Builder, HeaderBuilder}; +use kmr_common::{ + crypto, crypto::aes, crypto::hmac, crypto::KeyMaterial, crypto::OpaqueOr, keyblob, log_unimpl, + unimpl, Error, +}; +use kmr_wire::{keymint, rpc, secureclock::TimeStampToken, CborError}; +use log::error; + +use crate::rkp::serialize_cbor; + +/// Context used to derive the hardware backed key for computing HMAC in +/// IRemotelyProvisionedComponent. +pub const RPC_HMAC_KEY_CONTEXT: &[u8] = b"Key to MAC public keys"; + +/// Length (in bytes) of the HMAC key used in IRemotelyProvisionedComponent. +pub const RPC_HMAC_KEY_LEN: usize = 32; + +/// Combined collection of trait implementations that must be provided. +pub struct Implementation { + /// Retrieval of root key material. + pub keys: Box, + + /// Retrieval of attestation certificate signing information. + pub sign_info: Option>, + + /// Retrieval of attestation ID information. + pub attest_ids: Option>, + + /// Secure deletion secret manager. If not available, rollback-resistant + /// keys will not be supported. + pub sdd_mgr: Option>, + + /// Retrieval of bootloader status. + pub bootloader: Box, + + /// Storage key wrapping. If not available `convertStorageKeyToEphemeral()` will not be + /// supported + pub sk_wrapper: Option>, + + /// Trusted user presence indicator. + pub tup: Box, + + /// Legacy key conversion handling. + pub legacy_key: Option>, + + /// Retrieval of artifacts related to the device implementation of IRemotelyProvisionedComponent + /// (IRPC) HAL. + pub rpc: Box, +} + +/// Functionality related to retrieval of device-specific key material, and its subsequent use. +/// The caller is generally expected to drop the key material as soon as it is done with it. +pub trait RetrieveKeyMaterial { + /// Retrieve the root key used for derivation of a per-keyblob key encryption key (KEK), passing + /// in any opaque context. + fn root_kek(&self, context: &[u8]) -> Result, Error>; + + /// Retrieve any opaque (but non-confidential) context needed for future calls to `root_kek`. + /// Context should not include confidential data (it will be stored in the clear). + fn kek_context(&self) -> Result, Error> { + // Default implementation is to have an empty KEK retrieval context. + Ok(Vec::new()) + } + + /// Retrieve the key agreement key used for shared secret negotiation. + fn kak(&self) -> Result, Error>; + + /// Install the device HMAC agreed by shared secret negotiation into hardware (optional). + fn hmac_key_agreed(&self, _key: &crypto::hmac::Key) -> Option> { + // By default, use a software implementation that holds the key in memory. + None + } + + /// Retrieve the hardware backed secret used for UNIQUE_ID generation. + fn unique_id_hbk(&self, ckdf: &dyn crypto::Ckdf) -> Result { + // By default, use CKDF on the key agreement secret to derive a key. + let unique_id_label = b"UniqueID HBK 32B"; + ckdf.ckdf(&self.kak()?, unique_id_label, &[], 32).map(crypto::hmac::Key::new) + } + + /// Build the HMAC input for a [`TimeStampToken`]. The default implementation produces + /// data that matches the `ISecureClock` AIDL specification; this method should only be + /// overridden for back-compatibility reasons. + fn timestamp_token_mac_input(&self, token: &TimeStampToken) -> Result, Error> { + crate::clock::timestamp_token_mac_input(token) + } +} + +/// Device HMAC calculation. +pub trait DeviceHmac { + /// Calculate the HMAC over the data using the agreed device HMAC key. + fn hmac(&self, imp: &dyn crypto::Hmac, data: &[u8]) -> Result, Error>; + + /// Returns the key used for HMAC'ing data if available + fn get_hmac_key(&self) -> Option { + // By default we assume that the implementation cannot return a key + None + } +} + +/// Identification of which attestation signing key is required. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SigningKey { + /// Use a batch key that is shared across multiple devices (to prevent the keys being used as + /// device identifiers). + Batch, + /// Use a device-unique key for signing. Only supported for StrongBox. + DeviceUnique, +} + +/// Indication of preferred attestation signing algorithm. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SigningAlgorithm { + /// Prefer to sign with an elliptic curve key. + Ec, + /// Prefer to sign with an RSA key. + Rsa, +} + +/// Indication of required signing key. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct SigningKeyType { + /// Indicates the preferred type of signing key. + pub which: SigningKey, + /// Indicates what is going to be signed, to allow implementations to (optionally) use EC / RSA + /// signing keys for EC / RSA keys respectively. + pub algo_hint: SigningAlgorithm, +} + +/// Retrieval of attestation certificate signing information. The caller is expected to drop key +/// material after use, but may cache public key material. +pub trait RetrieveCertSigningInfo { + /// Return the signing key material for the specified `key_type`. The `algo_hint` parameter + /// indicates what is going to be signed, to allow implementations to (optionally) use EC / RSA + /// signing keys for EC /RSA keys respectively. + fn signing_key(&self, key_type: SigningKeyType) -> Result; + + /// Return the certificate chain associated with the specified signing key, where: + /// - `chain[0]` holds the public key that corresponds to `signing_key`, and which is signed + /// by... + /// - the keypair described by the second entry `chain[1]`, which in turn is signed by... + /// - ... + /// - the final certificate in the chain should be a self-signed cert holding a Google root. + fn cert_chain(&self, key_type: SigningKeyType) -> Result, Error>; +} + +/// Retrieval of attestation ID information. This information will not change (so the caller can +/// cache this information after first invocation). +pub trait RetrieveAttestationIds { + /// Return the attestation IDs associated with the device, if available. + fn get(&self) -> Result; + + /// Destroy all attestation IDs associated with the device. + fn destroy_all(&mut self) -> Result<(), Error>; +} + +/// Bootloader status. +pub trait BootloaderStatus { + /// Indication of whether bootloader processing is complete + fn done(&self) -> bool { + // By default assume that the bootloader is done before KeyMint starts. + true + } +} + +/// The trait that represents the device specific integration points required for the +/// implementation of IRemotelyProvisionedComponent (IRPC) HAL. +/// Note: The devices only supporting IRPC V3+ may ignore the optional IRPC V2 specific types in +/// the method signatures. +pub trait RetrieveRpcArtifacts { + /// Retrieve secret bytes (of the given output length) derived from a hardware backed key. + /// For a given context, the output is deterministic. + fn derive_bytes_from_hbk( + &self, + hkdf: &dyn crypto::Hkdf, + context: &[u8], + output_len: usize, + ) -> Result, Error>; + + /// Compute HMAC_SHA256 over the given input using a key derived from hardware. + fn compute_hmac_sha256( + &self, + hmac: &dyn crypto::Hmac, + hkdf: &dyn crypto::Hkdf, + input: &[u8], + ) -> Result, Error> { + let secret = self.derive_bytes_from_hbk(hkdf, RPC_HMAC_KEY_CONTEXT, RPC_HMAC_KEY_LEN)?; + crypto::hmac_sha256(hmac, &secret, input) + } + + /// Retrieve the information about the DICE chain belonging to the IRPC HAL implementation. + fn get_dice_info(&self, test_mode: rpc::TestMode) -> Result; + + /// Sign the input data with the CDI leaf private key of the IRPC HAL implementation. In IRPC V2, + /// the `data` to be signed is the `SignedMac_structure` in ProtectedData.aidl, when signing + /// the ephemeral MAC key used to authenticate the public keys. In IRPC V3, the `data` to be + /// signed is the `SignedDataSigStruct`. + /// If a particular implementation would like to return the signature in a COSE_Sign1 message, + /// they can mark this unimplemented and override the default implementation in the + /// `sign_data_in_cose_sign1` method below. + /// + /// The signature produced by this method should be in a format suitable for COSE structures: + /// - Ed25519 signatures are encoded as-is. + /// - NIST signatures are encoded as (r||s), with each value left-padded with zeroes to + /// the coordinate length. Note that this is a *different* format than is emitted by + /// the `kmr_common::crypto::Ec` trait. + /// + /// (The `kmr_common::crypto::ec::to_cose_signature()` function can help with this.) + fn sign_data( + &self, + ec: &dyn crypto::Ec, + data: &[u8], + rpc_v2: Option, + ) -> Result, Error>; + + /// Sign the payload and return a COSE_Sign1 message. In IRPC V2, the `payload` is the MAC Key. + /// In IRPC V3, the `payload` is the `Data` that the `SignedData` is parameterized with (i.e. a + /// CBOR array containing `challenge` and `CsrPayload`). + fn sign_data_in_cose_sign1( + &self, + ec: &dyn crypto::Ec, + signing_algorithm: &CsrSigningAlgorithm, + payload: &[u8], + _aad: &[u8], + _rpc_v2: Option, + ) -> Result, Error> { + let cose_sign_algorithm = match signing_algorithm { + CsrSigningAlgorithm::ES256 => iana::Algorithm::ES256, + CsrSigningAlgorithm::ES384 => iana::Algorithm::ES384, + CsrSigningAlgorithm::EdDSA => iana::Algorithm::EdDSA, + }; + // Construct `SignedData` + let protected = HeaderBuilder::new().algorithm(cose_sign_algorithm).build(); + let signed_data = CoseSign1Builder::new() + .protected(protected) + .payload(payload.to_vec()) + .try_create_signature(&[], |input| self.sign_data(ec, input, None))? + .build(); + let signed_data_cbor = signed_data.to_cbor_value().map_err(CborError::from)?; + serialize_cbor(&signed_data_cbor) + } +} + +/// Information about the DICE chain belonging to the implementation of the IRPC HAL. +#[derive(Clone)] +pub struct DiceInfo { + /// Public dice artifacts. + pub pub_dice_artifacts: PubDiceArtifacts, + /// Algorithm used for signing CSRs. + pub signing_algorithm: CsrSigningAlgorithm, + /// Test-mode CDI private key. + /// + /// This is only relevant for IRPC HAL V2 when `test_mode` is true. This is ignored in all other + /// cases. The optional test CDI private key may be set here, if the device implementers + /// do not want to cache the test CDI private key across the calls to the `get_dice_info` and + ///`sign_data` methods when creating the CSR. + pub rpc_v2_test_cdi_priv: Option, +} + +/// Algorithm used to sign with the CDI leaf private key. +#[derive(Clone, Copy, Debug)] +pub enum CsrSigningAlgorithm { + /// Sign with P-256 EC key. + ES256, + /// Sign with P-384 EC key. + ES384, + /// Sign with Ed25519 key. + EdDSA, +} + +/// Public DICE artifacts. +#[derive(Clone, Debug)] +pub struct PubDiceArtifacts { + /// Certificates for the UDS Pub encoded in CBOR as per `AdditionalDKSignatures` structure in + /// ProtectedData.aidl for IRPC HAL version 2 and as per `UdsCerts` structure in IRPC HAL + /// version 3. + pub uds_certs: Vec, + /// UDS Pub and the DICE certificates encoded in CBOR/COSE as per the `Bcc` structure + /// defined in ProtectedData.aidl for IRPC HAL version 2 and as per `DiceCertChain` structure + /// in IRPC HAL version 3. + pub dice_cert_chain: Vec, +} + +/// Enum distinguishing the two modes of operation for IRPC HAL V2, allowing an optional context +/// information to be passed in for the test mode. +pub enum RpcV2Req<'a> { + /// IRPC v2 request in production mode. + Production, + /// An opaque blob may be passed in for the test mode, if it was returned by the TA in + /// `RkpV2TestCDIPriv.context` in order to link the two requests: `get_dice_info` and `sign_data` + /// related to the same CSR. + Test(&'a [u8]), +} + +/// Struct encapsulating the optional CDI private key and the optional opaque context that may be +/// returned with `DiceInfo` in IRPC V2 test mode. +#[derive(Clone)] +pub struct RpcV2TestCDIPriv { + /// Test-mode CDI private key, if available. + pub test_cdi_priv: Option>, + /// An optional opaque blob set by the TA, if the TA wants a mechanism to relate the + /// two requests: `get_dice_info` and `sign_data` related to the same CSR. + pub context: Vec, +} + +/// Marker implementation for implementations that do not support `BOOTLOADER_ONLY` keys, which +/// always indicates that bootloader processing is complete. +pub struct BootloaderDone; +impl BootloaderStatus for BootloaderDone {} + +/// Trusted user presence indicator. +pub trait TrustedUserPresence { + /// Indication of whether user presence is detected, via a mechanism in the current secure + /// environment. + fn available(&self) -> bool { + // By default assume that trusted user presence is not supported. + false + } +} + +/// Marker implementation to indicate that trusted user presence is not supported. +pub struct TrustedPresenceUnsupported; +impl TrustedUserPresence for TrustedPresenceUnsupported {} + +/// Storage key wrapping. +pub trait StorageKeyWrapper { + /// Wrap the provided key material using an ephemeral storage key. + fn ephemeral_wrap(&self, key_material: &KeyMaterial) -> Result, Error>; +} + +// No-op implementations for the non-optional device traits. These implementations are only +// intended for convenience during the process of porting the KeyMint code to a new environment. + +/// Stub implementation of [`RetrieveKeyMaterial`]. +pub struct NoOpRetrieveKeyMaterial; +impl RetrieveKeyMaterial for NoOpRetrieveKeyMaterial { + fn root_kek(&self, _context: &[u8]) -> Result, Error> { + unimpl!(); + } + + fn kak(&self) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`RetrieveCertSigningInfo`]. +pub struct NoOpRetrieveCertSigningInfo; +impl RetrieveCertSigningInfo for NoOpRetrieveCertSigningInfo { + fn signing_key(&self, _key_type: SigningKeyType) -> Result { + unimpl!(); + } + + fn cert_chain(&self, _key_type: SigningKeyType) -> Result, Error> { + unimpl!(); + } +} + +/// Stub implementation of [`RetrieveRpcArtifacts`]. +pub struct NoOpRetrieveRpcArtifacts; +impl RetrieveRpcArtifacts for NoOpRetrieveRpcArtifacts { + fn derive_bytes_from_hbk( + &self, + _hkdf: &dyn crypto::Hkdf, + _context: &[u8], + _output_len: usize, + ) -> Result, Error> { + unimpl!(); + } + + fn get_dice_info<'a>(&self, _test_mode: rpc::TestMode) -> Result { + unimpl!(); + } + + fn sign_data( + &self, + _ec: &dyn crypto::Ec, + _data: &[u8], + _rpc_v2: Option, + ) -> Result, Error> { + unimpl!(); + } +} diff --git a/libs/rust/ta/src/keys.rs b/libs/rust/ta/src/keys.rs new file mode 100644 index 0000000..9971a49 --- /dev/null +++ b/libs/rust/ta/src/keys.rs @@ -0,0 +1,819 @@ +// Copyright 2022, 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. + +//! TA functionality related to key generation/import/upgrade. + +use crate::{cert, device, AttestationChainInfo}; +use Vec; +use core::{borrow::Borrow, cmp::Ordering, convert::TryFrom}; +use std::collections::btree_map::Entry; +use der::{referenced::RefToOwned, Decode, Sequence}; +use kmr_common::{ + crypto::{self, aes, rsa, KeyMaterial, OpaqueOr}, + der_err, get_bool_tag_value, get_opt_tag_value, get_tag_value, keyblob, km_err, tag, + try_to_vec, vec_try_with_capacity, Error, FallibleAllocExt, +}; +use kmr_wire::{ + keymint::{ + AttestationKey, Digest, EcCurve, ErrorCode, HardwareAuthenticatorType, KeyCharacteristics, + KeyCreationResult, KeyFormat, KeyOrigin, KeyParam, KeyPurpose, SecurityLevel, + UNDEFINED_NOT_AFTER, UNDEFINED_NOT_BEFORE, + }, + *, +}; +use log::{error, warn}; +use spki::SubjectPublicKeyInfoOwned; +use x509_cert::ext::pkix::KeyUsages; + +/// Maximum size of an attestation challenge value. +const MAX_ATTESTATION_CHALLENGE_LEN: usize = 128; + +/// Contents of wrapping key data +/// +/// ```asn1 +/// SecureKeyWrapper ::= SEQUENCE { +/// version INTEGER, # Value 0 +/// encryptedTransportKey OCTET_STRING, +/// initializationVector OCTET_STRING, +/// keyDescription KeyDescription, # See below +/// encryptedKey OCTET_STRING, +/// tag OCTET_STRING, +/// } +/// ``` +#[derive(Debug, Clone, Sequence)] +pub struct SecureKeyWrapper<'a> { + /// Version of this structure. + pub version: i32, + /// Encrypted transport key. + #[asn1(type = "OCTET STRING")] + pub encrypted_transport_key: &'a [u8], + /// IV to use for decryption. + #[asn1(type = "OCTET STRING")] + pub initialization_vector: &'a [u8], + /// Key parameters and description. + pub key_description: KeyDescription<'a>, + /// Ciphertext of the imported key. + #[asn1(type = "OCTET STRING")] + pub encrypted_key: &'a [u8], + /// Tag value. + #[asn1(type = "OCTET STRING")] + pub tag: &'a [u8], +} + +const SECURE_KEY_WRAPPER_VERSION: i32 = 0; + +/// Contents of key description. +/// +/// ```asn1 +/// KeyDescription ::= SEQUENCE { +/// keyFormat INTEGER, # Values from KeyFormat enum +/// keyParams AuthorizationList, # See cert.rs +/// } +/// ``` +#[derive(Debug, Clone, Sequence)] +pub struct KeyDescription<'a> { + /// Format of imported key. + pub key_format: i32, + /// Key parameters. + pub key_params: cert::AuthorizationList<'a>, +} + +/// Indication of whether key import has a secure wrapper. +#[derive(Debug, Clone, Copy)] +pub(crate) enum KeyImport { + Wrapped, + NonWrapped, +} + +/// Combined information needed for signing a fresh public key. +#[derive(Clone)] +pub(crate) struct SigningInfo<'a> { + pub attestation_info: Option<(&'a [u8], &'a [u8])>, // (challenge, app_id) + pub signing_key: KeyMaterial, + /// ASN.1 DER encoding of subject field from first cert. + pub issuer_subject: Vec, + /// Cert chain starting with public key for `signing_key`. + pub chain: Vec, +} + +impl crate::KeyMintTa { + /// Retrieve the signing information. + pub(crate) fn get_signing_info( + &self, + key_type: device::SigningKeyType, + ) -> Result { + let sign_info = self.dev.sign_info.as_ref().ok_or_else(|| { + km_err!(AttestationKeysNotProvisioned, "batch attestation keys not available") + })?; + // Retrieve the chain and issuer information, which is cached after first retrieval. + let mut attestation_chain_info = self.attestation_chain_info.borrow_mut(); + let chain_info = match attestation_chain_info.entry(key_type) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => { + // Retrieve and store the cert chain information (as this is public). + let chain = sign_info.cert_chain(key_type)?; + let issuer = + cert::extract_subject(chain.first().ok_or_else(|| { + km_err!(KeymintNotConfigured, "empty attestation chain") + })?)?; + e.insert(AttestationChainInfo { chain, issuer }) + } + }; + + // Retrieve the signing key information (which will be dropped when signing is done). + let signing_key = sign_info.signing_key(key_type)?; + Ok(SigningInfo { + attestation_info: None, + signing_key, + issuer_subject: chain_info.issuer.clone(), + chain: chain_info.chain.clone(), + }) + } + + /// Generate an X.509 leaf certificate. + pub(crate) fn generate_cert( + &self, + info: Option, + spki: SubjectPublicKeyInfoOwned, + params: &[KeyParam], + chars: &[KeyCharacteristics], + ) -> Result { + // Build and encode key usage extension value + let key_usage_ext_bits = cert::key_usage_extension_bits(params); + let key_usage_ext_val = cert::asn1_der_encode(&key_usage_ext_bits) + .map_err(|e| der_err!(e, "failed to encode KeyUsage {:?}", key_usage_ext_bits))?; + + // Build and encode basic constraints extension value, based on the key usage extension + // value + let basic_constraints_ext_val = + if (key_usage_ext_bits.0 & KeyUsages::KeyCertSign).bits().count_ones() != 0 { + let basic_constraints = cert::basic_constraints_ext_value(true); + Some(cert::asn1_der_encode(&basic_constraints).map_err(|e| { + der_err!(e, "failed to encode basic constraints {:?}", basic_constraints) + })?) + } else { + None + }; + + // Build and encode attestation extension if present + let id_info = self.get_attestation_ids(); + let attest_ext_val = + if let Some(SigningInfo { attestation_info: Some((challenge, app_id)), .. }) = &info { + let unique_id = self.calculate_unique_id(app_id, params)?; + let boot_info = self.boot_info_hashed_key()?; + let attest_ext = cert::attestation_extension( + self.aidl_version as i32, + challenge, + app_id, + self.hw_info.security_level, + id_info.as_ref().map(|v| v.borrow()), + params, + chars, + &unique_id, + &boot_info, + &self.additional_attestation_info, + )?; + Some( + cert::asn1_der_encode(&attest_ext) + .map_err(|e| der_err!(e, "failed to encode attestation extension"))?, + ) + } else { + None + }; + + let tbs_cert = cert::tbs_certificate( + &info, + spki, + &key_usage_ext_val, + basic_constraints_ext_val.as_deref(), + attest_ext_val.as_deref(), + tag::characteristics_at(chars, self.hw_info.security_level)?, + params, + )?; + let tbs_data = cert::asn1_der_encode(&tbs_cert) + .map_err(|e| der_err!(e, "failed to encode tbsCert"))?; + // If key does not have ATTEST_KEY or SIGN purpose, the certificate has empty signature + let sig_data = match info.as_ref() { + Some(info) => self.sign_cert_data(info.signing_key.clone(), tbs_data.as_slice())?, + None => Vec::new(), + }; + + let cert = cert::certificate(tbs_cert, &sig_data)?; + let cert_data = cert::asn1_der_encode(&cert) + .map_err(|e| der_err!(e, "failed to encode certificate"))?; + Ok(keymint::Certificate { encoded_certificate: cert_data }) + } + + /// Perform a complete signing operation using default modes. + fn sign_cert_data(&self, signing_key: KeyMaterial, tbs_data: &[u8]) -> Result, Error> { + match signing_key { + KeyMaterial::Rsa(key) => { + let mut op = self + .imp + .rsa + .begin_sign(key, rsa::SignMode::Pkcs1_1_5Padding(Digest::Sha256))?; + op.update(tbs_data)?; + op.finish() + } + KeyMaterial::Ec(curve, _, key) => { + let digest = if curve == EcCurve::Curve25519 { + // Ed25519 includes an internal digest and so does not use an external digest. + Digest::None + } else { + Digest::Sha256 + }; + let mut op = self.imp.ec.begin_sign(key, digest)?; + op.update(tbs_data)?; + op.finish() + } + _ => Err(km_err!(IncompatibleAlgorithm, "unexpected cert signing key type")), + } + } + + /// Calculate the `UNIQUE_ID` value for the parameters, if needed. + fn calculate_unique_id(&self, app_id: &[u8], params: &[KeyParam]) -> Result, Error> { + if !get_bool_tag_value!(params, IncludeUniqueId)? { + return Ok(Vec::new()); + } + let creation_datetime = + get_tag_value!(params, CreationDatetime, ErrorCode::InvalidArgument)?; + let rounded_datetime = creation_datetime.ms_since_epoch / 2_592_000_000i64; + let datetime_data = rounded_datetime.to_ne_bytes(); + + let mut combined_input = vec_try_with_capacity!(datetime_data.len() + app_id.len() + 1)?; + combined_input.extend_from_slice(&datetime_data[..]); + combined_input.extend_from_slice(app_id); + combined_input.push(u8::from(get_bool_tag_value!(params, ResetSinceIdRotation)?)); + + let hbk = self.dev.keys.unique_id_hbk(&*self.imp.ckdf)?; + + let mut hmac_op = self.imp.hmac.begin(hbk.into(), Digest::Sha256)?; + hmac_op.update(&combined_input)?; + let tag = hmac_op.finish()?; + try_to_vec(&tag[..16]) + } + + pub(crate) fn generate_key( + &mut self, + params: &[KeyParam], + attestation_key: Option, + ) -> Result { + let (key_material, chars) = self.generate_key_material(params)?; + self.finish_keyblob_creation( + params, + attestation_key, + chars, + key_material, + keyblob::SlotPurpose::KeyGeneration, + ) + } + + pub(crate) fn generate_key_material( + &mut self, + params: &[KeyParam], + ) -> Result<(KeyMaterial, Vec), Error> { + let (mut chars, keygen_info) = tag::extract_key_gen_characteristics( + self.secure_storage_available(), + params, + self.hw_info.security_level, + )?; + self.add_keymint_tags(&mut chars, KeyOrigin::Generated)?; + let key_material = match keygen_info { + crypto::KeyGenInfo::Aes(variant) => { + self.imp.aes.generate_key(&mut *self.imp.rng, variant, params)? + } + crypto::KeyGenInfo::TripleDes => { + self.imp.des.generate_key(&mut *self.imp.rng, params)? + } + crypto::KeyGenInfo::Hmac(key_size) => { + self.imp.hmac.generate_key(&mut *self.imp.rng, key_size, params)? + } + crypto::KeyGenInfo::Rsa(key_size, pub_exponent) => { + self.imp.rsa.generate_key(&mut *self.imp.rng, key_size, pub_exponent, params)? + } + crypto::KeyGenInfo::NistEc(curve) => { + self.imp.ec.generate_nist_key(&mut *self.imp.rng, curve, params)? + } + crypto::KeyGenInfo::Ed25519 => { + self.imp.ec.generate_ed25519_key(&mut *self.imp.rng, params)? + } + crypto::KeyGenInfo::X25519 => { + self.imp.ec.generate_x25519_key(&mut *self.imp.rng, params)? + } + }; + Ok((key_material, chars)) + } + + pub(crate) fn import_key( + &mut self, + params: &[KeyParam], + key_format: KeyFormat, + key_data: &[u8], + attestation_key: Option, + import_type: KeyImport, + ) -> Result { + if !self.in_early_boot && get_bool_tag_value!(params, EarlyBootOnly)? { + return Err(km_err!(EarlyBootEnded, "attempt to use EARLY_BOOT key after early boot")); + } + + let (mut chars, key_material) = tag::extract_key_import_characteristics( + &self.imp, + self.secure_storage_available(), + params, + self.hw_info.security_level, + key_format, + key_data, + )?; + match import_type { + KeyImport::NonWrapped => { + self.add_keymint_tags(&mut chars, KeyOrigin::Imported)?; + } + KeyImport::Wrapped => { + self.add_keymint_tags(&mut chars, KeyOrigin::SecurelyImported)?; + } + } + + self.finish_keyblob_creation( + params, + attestation_key, + chars, + key_material, + keyblob::SlotPurpose::KeyImport, + ) + } + + /// Perform common processing for keyblob creation (for both generation and import). + pub fn finish_keyblob_creation( + &mut self, + params: &[KeyParam], + attestation_key: Option, + chars: Vec, + key_material: KeyMaterial, + purpose: keyblob::SlotPurpose, + ) -> Result { + let keyblob = keyblob::PlaintextKeyBlob { + // Don't include any `SecurityLevel::Keystore` characteristics in the set that is bound + // to the key. + characteristics: chars + .iter() + .filter(|c| c.security_level != SecurityLevel::Keystore) + .cloned() + .collect(), + key_material: key_material.clone(), + }; + let attest_keyblob; + let mut certificate_chain = Vec::new(); + if let Some(spki) = keyblob.key_material.subject_public_key_info( + &mut Vec::::new(), + &*self.imp.ec, + &*self.imp.rsa, + )? { + // Asymmetric keys return the public key inside an X.509 certificate. + // Need to determine: + // - a key to sign the cert with (may be absent), together with any associated + // cert chain to append + // - whether to include an attestation extension + let attest_challenge = get_opt_tag_value!(params, AttestationChallenge)?; + + let signing_info = if let Some(attest_challenge) = attest_challenge { + // Attestation requested. + if attest_challenge.len() > MAX_ATTESTATION_CHALLENGE_LEN { + return Err(km_err!( + InvalidInputLength, + "attestation challenge too large: {} bytes", + attest_challenge.len() + )); + } + let attest_app_id = get_opt_tag_value!(params, AttestationApplicationId)? + .ok_or_else(|| { + km_err!(AttestationApplicationIdMissing, "attestation requested") + })?; + let attestation_info: Option<(&[u8], &[u8])> = + Some((attest_challenge, attest_app_id)); + + if let Some(attest_keyinfo) = attestation_key.as_ref() { + // User-specified attestation key provided. + (attest_keyblob, _) = self.keyblob_parse_decrypt( + &attest_keyinfo.key_blob, + &attest_keyinfo.attest_key_params, + )?; + attest_keyblob + .suitable_for(KeyPurpose::AttestKey, self.hw_info.security_level)?; + if attest_keyinfo.issuer_subject_name.is_empty() { + return Err(km_err!(InvalidArgument, "empty subject name")); + } + Some(SigningInfo { + attestation_info, + signing_key: attest_keyblob.key_material, + issuer_subject: attest_keyinfo.issuer_subject_name.clone(), + chain: Vec::new(), + }) + } else { + // Need to use a device key for attestation. Look up the relevant device key and + // chain. + let which_key = match ( + get_bool_tag_value!(params, DeviceUniqueAttestation)?, + self.is_strongbox(), + ) { + (false, _) => device::SigningKey::Batch, + (true, true) => device::SigningKey::DeviceUnique, + (true, false) => { + return Err(km_err!( + InvalidArgument, + "device unique attestation supported only by Strongbox TA" + )) + } + }; + // Provide an indication of what's going to be signed, to allow the + // implementation to switch between EC and RSA signing keys if it so chooses. + let algo_hint = match &keyblob.key_material { + crypto::KeyMaterial::Rsa(_) => device::SigningAlgorithm::Rsa, + crypto::KeyMaterial::Ec(_, _, _) => device::SigningAlgorithm::Ec, + _ => return Err(km_err!(InvalidArgument, "unexpected key type!")), + }; + + let mut info = self + .get_signing_info(device::SigningKeyType { which: which_key, algo_hint })?; + info.attestation_info = attestation_info; + Some(info) + } + } else { + // No attestation challenge, so no attestation. + if attestation_key.is_some() { + return Err(km_err!( + AttestationChallengeMissing, + "got attestation key but no challenge" + )); + } + + // See if the generated key can self-sign. + let is_signing_key = params.iter().any(|param| { + matches!( + param, + KeyParam::Purpose(KeyPurpose::Sign) + | KeyParam::Purpose(KeyPurpose::AttestKey) + ) + }); + if is_signing_key { + Some(SigningInfo { + attestation_info: None, + signing_key: key_material, + issuer_subject: try_to_vec(tag::get_cert_subject(params)?)?, + chain: Vec::new(), + }) + } else { + None + } + }; + + // Build the X.509 leaf certificate. + let leaf_cert = + self.generate_cert(signing_info.clone(), spki.ref_to_owned(), params, &chars)?; + certificate_chain.try_push(leaf_cert)?; + + // Append the rest of the chain. + if let Some(info) = signing_info { + for cert in info.chain { + certificate_chain.try_push(cert)?; + } + } + } + + // Now build the keyblob. + let kek_context = self.dev.keys.kek_context()?; + let root_kek = self.root_kek(&kek_context)?; + let hidden = tag::hidden(params, self.root_of_trust()?)?; + let encrypted_keyblob = keyblob::encrypt( + self.hw_info.security_level, + match &mut self.dev.sdd_mgr { + None => None, + Some(mr) => Some(&mut **mr), + }, + &*self.imp.aes, + &*self.imp.hkdf, + &mut *self.imp.rng, + &root_kek, + &kek_context, + keyblob, + hidden, + purpose, + )?; + let serialized_keyblob = encrypted_keyblob.into_vec()?; + + Ok(KeyCreationResult { + key_blob: serialized_keyblob, + key_characteristics: chars, + certificate_chain, + }) + } + + pub(crate) fn import_wrapped_key( + &mut self, + wrapped_key_data: &[u8], + wrapping_key_blob: &[u8], + masking_key: &[u8], + unwrapping_params: &[KeyParam], + password_sid: i64, + biometric_sid: i64, + ) -> Result { + // Decrypt the wrapping key blob + let (wrapping_key, _) = self.keyblob_parse_decrypt(wrapping_key_blob, unwrapping_params)?; + let keyblob::PlaintextKeyBlob { characteristics, key_material } = wrapping_key; + + // Decode the ASN.1 DER encoded `SecureKeyWrapper`. + let mut secure_key_wrapper = SecureKeyWrapper::from_der(wrapped_key_data) + .map_err(|e| der_err!(e, "failed to parse SecureKeyWrapper"))?; + + if secure_key_wrapper.version != SECURE_KEY_WRAPPER_VERSION { + return Err(km_err!(InvalidArgument, "invalid version in Secure Key Wrapper.")); + } + + // Decrypt the masked transport key, using an RSA key. (Only RSA wrapping keys are supported + // by the spec, as RSA is the only algorithm supporting asymmetric decryption.) + let masked_transport_key = match key_material { + KeyMaterial::Rsa(key) => { + // Check the requirements on the wrapping key characterisitcs + let decrypt_mode = tag::check_rsa_wrapping_key_params( + tag::characteristics_at(&characteristics, self.hw_info.security_level)?, + unwrapping_params, + )?; + + // Decrypt the masked and encrypted transport key + let mut crypto_op = self.imp.rsa.begin_decrypt(key, decrypt_mode)?; + crypto_op.as_mut().update(secure_key_wrapper.encrypted_transport_key)?; + crypto_op.finish()? + } + _ => { + return Err(km_err!(InvalidArgument, "invalid key algorithm for transport key")); + } + }; + + if masked_transport_key.len() != masking_key.len() { + return Err(km_err!( + InvalidArgument, + "masked transport key is {} bytes, but masking key is {} bytes", + masked_transport_key.len(), + masking_key.len() + )); + } + + let unmasked_transport_key: Vec = + masked_transport_key.iter().zip(masking_key).map(|(x, y)| x ^ y).collect(); + + let aes_transport_key = + aes::Key::Aes256(unmasked_transport_key.try_into().map_err(|_e| { + km_err!( + InvalidArgument, + "transport key len {} not correct for AES-256 key", + masked_transport_key.len() + ) + })?); + + // Validate the size of the IV and match the `aes::GcmMode` based on the tag size. + let iv_len = secure_key_wrapper.initialization_vector.len(); + if iv_len != aes::GCM_NONCE_SIZE { + return Err(km_err!( + InvalidArgument, + "IV length is of {} bytes, which should be of {} bytes", + iv_len, + aes::GCM_NONCE_SIZE + )); + } + let tag_len = secure_key_wrapper.tag.len(); + let gcm_mode = match tag_len { + 12 => crypto::aes::GcmMode::GcmTag12 { + nonce: secure_key_wrapper.initialization_vector.try_into() + .unwrap(/* safe: len checked */), + }, + 13 => crypto::aes::GcmMode::GcmTag13 { + nonce: secure_key_wrapper.initialization_vector.try_into() + .unwrap(/* safe: len checked */), + }, + 14 => crypto::aes::GcmMode::GcmTag14 { + nonce: secure_key_wrapper.initialization_vector.try_into() + .unwrap(/* safe: len checked */), + }, + 15 => crypto::aes::GcmMode::GcmTag15 { + nonce: secure_key_wrapper.initialization_vector.try_into() + .unwrap(/* safe: len checked */), + }, + 16 => crypto::aes::GcmMode::GcmTag16 { + nonce: secure_key_wrapper.initialization_vector.try_into() + .unwrap(/* safe: len checked */), + }, + v => { + return Err(km_err!( + InvalidMacLength, + "want 12-16 byte tag for AES-GCM not {} bytes", + v + )) + } + }; + + // Decrypt the encrypted key to be imported, using the ASN.1 DER (re-)encoding of the key + // description as the AAD. + let mut op = self.imp.aes.begin_aead( + OpaqueOr::Explicit(aes_transport_key), + gcm_mode, + crypto::SymmetricOperation::Decrypt, + )?; + op.update_aad( + &cert::asn1_der_encode(&secure_key_wrapper.key_description) + .map_err(|e| der_err!(e, "failed to re-encode SecureKeyWrapper"))?, + )?; + + let mut imported_key_data = op.update(secure_key_wrapper.encrypted_key)?; + imported_key_data.try_extend_from_slice(&op.update(secure_key_wrapper.tag)?)?; + imported_key_data.try_extend_from_slice(&op.finish()?)?; + + // The `Cow::to_mut()` call will not clone, because `from_der()` invokes + // `AuthorizationList::decode_value()` which creates the owned variant. + let imported_key_params: &mut Vec = + secure_key_wrapper.key_description.key_params.auths.to_mut(); + if let Some(secure_id) = get_opt_tag_value!(&*imported_key_params, UserSecureId)? { + let secure_id = *secure_id; + // If both the Password and Fingerprint bits are set in UserSecureId, the password SID + // should be used, because biometric auth tokens contain both password and fingerprint + // SIDs, but password auth tokens only contain the password SID. + if (secure_id & (HardwareAuthenticatorType::Password as u64) + == (HardwareAuthenticatorType::Password as u64)) + && (secure_id & (HardwareAuthenticatorType::Fingerprint as u64) + == (HardwareAuthenticatorType::Fingerprint as u64)) + { + imported_key_params + .retain(|key_param| !matches!(key_param, KeyParam::UserSecureId(_))); + imported_key_params.try_push(KeyParam::UserSecureId(password_sid as u64))?; + } else if secure_id & (HardwareAuthenticatorType::Password as u64) + == (HardwareAuthenticatorType::Password as u64) + { + imported_key_params + .retain(|key_param| !matches!(key_param, KeyParam::UserSecureId(_))); + imported_key_params.try_push(KeyParam::UserSecureId(password_sid as u64))?; + } else if secure_id & (HardwareAuthenticatorType::Fingerprint as u64) + == (HardwareAuthenticatorType::Fingerprint as u64) + { + imported_key_params + .retain(|key_param| !matches!(key_param, KeyParam::UserSecureId(_))); + imported_key_params.try_push(KeyParam::UserSecureId(biometric_sid as u64))?; + } + }; + + // There is no way for clients to pass CERTIFICATE_NOT_BEFORE and CERTIFICATE_NOT_AFTER. + // importWrappedKey must use validity with no well-defined expiration date. + imported_key_params.try_push(KeyParam::CertificateNotBefore(UNDEFINED_NOT_BEFORE))?; + imported_key_params.try_push(KeyParam::CertificateNotAfter(UNDEFINED_NOT_AFTER))?; + + self.import_key( + imported_key_params, + KeyFormat::try_from(secure_key_wrapper.key_description.key_format).map_err(|_e| { + km_err!( + UnsupportedKeyFormat, + "could not convert the provided keyformat {}", + secure_key_wrapper.key_description.key_format + ) + })?, + &imported_key_data, + None, + KeyImport::Wrapped, + ) + } + + pub(crate) fn upgrade_key( + &mut self, + keyblob_to_upgrade: &[u8], + upgrade_params: Vec, + ) -> Result, Error> { + let (mut keyblob, mut modified) = + match self.keyblob_parse_decrypt_backlevel(keyblob_to_upgrade, &upgrade_params) { + Ok(v) => (v.0, false), + Err(Error::Hal(ErrorCode::KeyRequiresUpgrade, _)) => { + // Because `keyblob_parse_decrypt_backlevel` explicitly allows back-level + // versioned keys, a `KeyRequiresUpgrade` error indicates that the keyblob looks + // to be in legacy format. Try to convert it. + let legacy_handler = + self.dev.legacy_key.as_mut().ok_or_else(|| { + km_err!(KeymintNotConfigured, "no legacy key handler") + })?; + ( + legacy_handler.convert_legacy_key( + keyblob_to_upgrade, + &upgrade_params, + self.boot_info + .as_ref() + .ok_or_else(|| km_err!(HardwareNotYetAvailable, "no boot info"))?, + self.hw_info.security_level, + )?, + // Force the emission of a new keyblob even if versions are the same. + true, + ) + } + Err(e) => return Err(e), + }; + + fn upgrade(v: &mut u32, curr: u32, name: &str) -> Result { + match (*v).cmp(&curr) { + Ordering::Less => { + *v = curr; + Ok(true) + } + Ordering::Equal => Ok(false), + Ordering::Greater => { + error!("refusing to downgrade {} from {} to {}", name, v, curr); + Err(km_err!( + InvalidArgument, + "keyblob with future {} {} (current {})", + name, + v, + curr + )) + } + } + } + + for chars in &mut keyblob.characteristics { + if chars.security_level != self.hw_info.security_level { + continue; + } + for param in &mut chars.authorizations { + match param { + KeyParam::OsVersion(v) => { + if let Some(hal_info) = &self.hal_info { + if hal_info.os_version == 0 { + // Special case: upgrades to OS version zero are always allowed. + warn!("forcing upgrade to OS version 0"); + modified |= *v != 0; + *v = 0; + } else { + modified |= upgrade(v, hal_info.os_version, "OS version")?; + } + } else { + error!("OS version not available, can't upgrade from {}", v); + } + } + KeyParam::OsPatchlevel(v) => { + if let Some(hal_info) = &self.hal_info { + modified |= upgrade(v, hal_info.os_patchlevel, "OS patchlevel")?; + } else { + error!("OS patchlevel not available, can't upgrade from {}", v); + } + } + KeyParam::VendorPatchlevel(v) => { + if let Some(hal_info) = &self.hal_info { + modified |= + upgrade(v, hal_info.vendor_patchlevel, "vendor patchlevel")?; + } else { + error!("vendor patchlevel not available, can't upgrade from {}", v); + } + } + KeyParam::BootPatchlevel(v) => { + if let Some(boot_info) = &self.boot_info { + modified |= upgrade(v, boot_info.boot_patchlevel, "boot patchlevel")?; + } else { + error!("boot patchlevel not available, can't upgrade from {}", v); + } + } + _ => {} + } + } + } + + if !modified { + // No upgrade needed, return empty data to indicate existing keyblob can still be used. + return Ok(Vec::new()); + } + + // Now re-build the keyblob. Use a potentially fresh key encryption key, and potentially a + // new secure deletion secret slot. (The old slot will be released when Keystore performs + // the corresponding `deleteKey` operation on the old keyblob. + let kek_context = self.dev.keys.kek_context()?; + let root_kek = self.root_kek(&kek_context)?; + let hidden = tag::hidden(&upgrade_params, self.root_of_trust()?)?; + let encrypted_keyblob = keyblob::encrypt( + self.hw_info.security_level, + match &mut self.dev.sdd_mgr { + None => None, + Some(mr) => Some(&mut **mr), + }, + &*self.imp.aes, + &*self.imp.hkdf, + &mut *self.imp.rng, + &root_kek, + &kek_context, + keyblob, + hidden, + keyblob::SlotPurpose::KeyUpgrade, + )?; + Ok(encrypted_keyblob.into_vec()?) + } +} diff --git a/libs/rust/ta/src/lib.rs b/libs/rust/ta/src/lib.rs new file mode 100644 index 0000000..357e0a1 --- /dev/null +++ b/libs/rust/ta/src/lib.rs @@ -0,0 +1,1327 @@ +// Copyright 2022, 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. + +//! KeyMint trusted application (TA) implementation. + + +use core::cmp::Ordering; +use core::mem::size_of; +use core::{cell::RefCell, convert::TryFrom}; +use std::collections::BTreeMap; +use std::rc::Rc; +use device::DiceInfo; +use kmr_common::{ + crypto::{self, hmac, OpaqueOr}, + get_bool_tag_value, + keyblob::{self, RootOfTrustInfo, SecureDeletionSlot}, + km_err, tag, try_to_vec, vec_try, vec_try_with_capacity, Error, FallibleAllocExt, +}; +use kmr_wire::{ + coset::TaggedCborSerializable, + keymint::{ + Digest, ErrorCode, HardwareAuthToken, KeyCharacteristics, KeyMintHardwareInfo, KeyOrigin, + KeyParam, SecurityLevel, Tag, VerifiedBootState, NEXT_MESSAGE_SIGNAL_FALSE, + NEXT_MESSAGE_SIGNAL_TRUE, + }, + rpc, + rpc::{EekCurve, IRPC_V2, IRPC_V3}, + sharedsecret::SharedSecretParameters, + *, +}; +use log::{debug, error, info, trace, warn}; + +mod cert; +mod clock; +pub mod device; +pub mod keys; +mod operation; +pub mod rkp; +mod secret; + +use keys::KeyImport; +use operation::{OpHandle, Operation}; + +#[cfg(test)] +mod tests; + +/// Possible KeyMint HAL versions +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum KeyMintHalVersion { + /// V4 adds support for attestation of module information. + V4 = 400, + /// V3 adds support for attestation of second IMEI value. + V3 = 300, + /// V2 adds support for curve 25519 and root-of-trust transfer. + V2 = 200, + /// V1 is the initial version of the KeyMint HAL. + V1 = 100, +} + +/// Version code for current KeyMint. +pub const KEYMINT_CURRENT_VERSION: KeyMintHalVersion = KeyMintHalVersion::V4; + +/// Maximum number of parallel operations supported when running as TEE. +const MAX_TEE_OPERATIONS: usize = 16; + +/// Maximum number of parallel operations supported when running as StrongBox. +const MAX_STRONGBOX_OPERATIONS: usize = 4; + +/// Maximum number of keys whose use count can be tracked. +const MAX_USE_COUNTED_KEYS: usize = 32; + +/// Tags allowed in `KeyMintTa::additional_attestation_info`. +const ALLOWED_ADDITIONAL_ATTESTATION_TAGS: &[Tag] = &[Tag::ModuleHash]; + +/// Per-key ID use count. +struct UseCount { + key_id: KeyId, + count: u64, +} + +/// Attestation chain information. +struct AttestationChainInfo { + /// Chain of certificates from intermediate to root. + chain: Vec, + /// Subject field from the first certificate in the chain, as an ASN.1 DER encoded `Name` (cf + /// RFC 5280 s4.1.2.4). + issuer: Vec, +} + +/// KeyMint device implementation, running in secure environment. +pub struct KeyMintTa { + /** + * State that is fixed on construction. + */ + + /// Trait objects that hold this device's implementations of the abstract cryptographic + /// functionality traits. + imp: crypto::Implementation, + + /// Trait objects that hold this device's implementations of per-device functionality. + dev: device::Implementation, + + /// Information about this particular KeyMint implementation's hardware. + hw_info: HardwareInfo, + + /// Information about the implementation of the IRemotelyProvisionedComponent (IRPC) HAL. + rpc_info: RpcInfo, + + /// The version of the HAL AIDL interface specification that this TA acts as. + aidl_version: KeyMintHalVersion, + + /** + * State that is set after the TA starts, but latched thereafter. + */ + + /// Parameters for shared secret negotiation. + shared_secret_params: Option, + + /// Information provided by the bootloader once at start of day. + boot_info: Option, + rot_data: Option>, + + /// Information provided by the HAL service once at start of day. + hal_info: Option, + + /// Additional information to attest to, provided by Android. Refer to + /// `IKeyMintDevice::setAdditionalAttestationInfo()`. + additional_attestation_info: Vec, + + /// Attestation chain information, retrieved on first use. + attestation_chain_info: RefCell>, + + /// Attestation ID information, fixed forever for a device, but retrieved on first use. + attestation_id_info: RefCell>>, + + /// Public DICE artifacts (UDS certs and the DICE chain) included in the certificate signing + /// requests (CSR) and the algorithm used to sign the CSR for IRemotelyProvisionedComponent + /// (IRPC) HAL. Fixed for a device. Retrieved on first use. + /// + /// Note: This information is cached only in the implementations of IRPC HAL V3 and + /// IRPC HAL V2 in production mode. + dice_info: RefCell>>, + + /// Whether the device is still in early-boot. + in_early_boot: bool, + + /// Device HMAC implementation which uses the `ISharedSecret` negotiated key. + device_hmac: Option>, + + /** + * State that changes during operation. + */ + + /// Challenge for root-of-trust transfer (StrongBox only). + rot_challenge: [u8; 16], + + /// The operation table. + operations: Vec>, + + /// Use counts for keys where this is tracked. + use_count: [Option; MAX_USE_COUNTED_KEYS], + + /// Operation handle of the (single) in-flight operation that requires trusted user presence. + presence_required_op: Option, +} + +/// A helper method that can be used by the TA for processing the responses to be sent to the +/// HAL service. Splits large response messages into multiple parts based on the capacity of the +/// channel from the TA to the HAL. One element in the returned response array consists of: +/// where next_msg_signal is a byte whose value is 1 if there are +/// more messages in the response array following this one. This signal should be used by the HAL +/// side to decide whether or not to wait for more messages. Implementation of this method must be +/// in sync with its counterpart in the `kmr-hal` crate. +pub fn split_rsp(mut rsp_data: &[u8], max_size: usize) -> Result>, Error> { + if rsp_data.is_empty() || max_size < 2 { + return Err(km_err!( + InvalidArgument, + "response data is empty or max size: {} is invalid", + max_size + )); + } + // Need to allocate one byte for the more_msg_signal. + let allowed_msg_length = max_size - 1; + let mut num_of_splits = rsp_data.len() / allowed_msg_length; + if rsp_data.len() % allowed_msg_length > 0 { + num_of_splits += 1; + } + let mut split_rsp = vec_try_with_capacity!(num_of_splits)?; + while rsp_data.len() > allowed_msg_length { + let mut rsp = vec_try_with_capacity!(allowed_msg_length + 1)?; + rsp.push(NEXT_MESSAGE_SIGNAL_TRUE); + rsp.extend_from_slice(&rsp_data[..allowed_msg_length]); + trace!("Current response size with signalling byte: {}", rsp.len()); + split_rsp.push(rsp); + rsp_data = &rsp_data[allowed_msg_length..]; + } + let mut last_rsp = vec_try_with_capacity!(rsp_data.len() + 1)?; + last_rsp.push(NEXT_MESSAGE_SIGNAL_FALSE); + last_rsp.extend_from_slice(rsp_data); + split_rsp.push(last_rsp); + Ok(split_rsp) +} + +/// Hardware information. +#[derive(Clone, Debug)] +pub struct HardwareInfo { + // Fields that correspond to the HAL `KeyMintHardwareInfo` type. + /// Security level that this KeyMint implementation is running at. + pub security_level: SecurityLevel, + /// Version number. + pub version_number: i32, + /// KeyMint implementation name. + pub impl_name: &'static str, + /// Author of KeyMint implementation. + pub author_name: &'static str, + /// Unique identifier for this KeyMint. + pub unique_id: &'static str, + // The `timestamp_token_required` field in `KeyMintHardwareInfo` is skipped here because it gets + // set depending on whether a local clock is available. +} + +/// Information required to construct the structures defined in RpcHardwareInfo.aidl +/// and DeviceInfo.aidl, for IRemotelyProvisionedComponent (IRPC) HAL V2. +#[derive(Debug)] +pub struct RpcInfoV2 { + // Fields used in `RpcHardwareInfo.aidl`: + /// Author of KeyMint implementation. + pub author_name: &'static str, + /// EEK curve supported by this implementation. + pub supported_eek_curve: EekCurve, + /// Unique identifier for this KeyMint. + pub unique_id: &'static str, + /// Indication of whether secure boot is enforced for the processor running this code. + /// Used as `DeviceInfo.fused`. + pub fused: bool, +} + +/// Information required to construct the structures defined in RpcHardwareInfo.aidl +/// and DeviceInfo.aidl, for IRemotelyProvisionedComponent (IRPC) HAL V3. +#[derive(Debug)] +pub struct RpcInfoV3 { + // Fields used in `RpcHardwareInfo.aidl`: + /// Author of KeyMint implementation. + pub author_name: &'static str, + /// Unique identifier for this KeyMint. + pub unique_id: &'static str, + /// Indication of whether secure boot is enforced for the processor running this code. + /// Used as `DeviceInfo.fused`. + pub fused: bool, + /// Supported number of keys in a CSR. + pub supported_num_of_keys_in_csr: i32, +} + +/// Enum to distinguish the set of information required for different versions of IRPC HAL +/// implementations +pub enum RpcInfo { + /// Information for v2 of the IRPC HAL. + V2(RpcInfoV2), + /// Information for v3 of the IRPC HAL. + V3(RpcInfoV3), +} + +impl RpcInfo { + /// Indicate the HAL version of RPC information. + pub fn get_version(&self) -> i32 { + match self { + RpcInfo::V2(_) => IRPC_V2, + RpcInfo::V3(_) => IRPC_V3, + } + } +} + +/// Information provided once at service start by the HAL service, describing +/// the state of the userspace operating system (which may change from boot to +/// boot, e.g. for running GSI). +#[derive(Clone, Copy, Debug)] +pub struct HalInfo { + /// OS version. + pub os_version: u32, + /// OS patchlevel, in YYYYMM format. + pub os_patchlevel: u32, + /// Vendor patchlevel, in YYYYMMDD format + pub vendor_patchlevel: u32, +} + +/// Identifier for a keyblob. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +struct KeyId([u8; 32]); + +impl KeyMintTa { + /// Create a new [`KeyMintTa`] instance. + pub fn new( + hw_info: HardwareInfo, + rpc_info: RpcInfo, + imp: crypto::Implementation, + dev: device::Implementation, + ) -> Self { + let max_operations = if hw_info.security_level == SecurityLevel::Strongbox { + MAX_STRONGBOX_OPERATIONS + } else { + MAX_TEE_OPERATIONS + }; + Self { + imp, + dev, + in_early_boot: true, + device_hmac: None, + rot_challenge: [0; 16], + // Work around Rust limitation that `vec![None; n]` doesn't work. + operations: (0..max_operations).map(|_| None).collect(), + use_count: Default::default(), + presence_required_op: None, + shared_secret_params: None, + hw_info, + rpc_info, + aidl_version: KEYMINT_CURRENT_VERSION, + boot_info: None, + rot_data: None, + hal_info: None, + attestation_chain_info: RefCell::new(BTreeMap::new()), + attestation_id_info: RefCell::new(None), + dice_info: RefCell::new(None), + additional_attestation_info: Vec::new(), + } + } + + /// Returns key used to sign auth tokens + pub fn get_hmac_key(&self) -> Option { + match &self.device_hmac { + Some(device_hmac) => device_hmac.get_hmac_key(), + None => None, + } + } + + /// Indicate whether the current device is acting as a StrongBox instance. + pub fn is_strongbox(&self) -> bool { + self.hw_info.security_level == SecurityLevel::Strongbox + } + + /// Indicate whether the current device has secure storage available. + fn secure_storage_available(&self) -> kmr_common::tag::SecureStorage { + if self.dev.sdd_mgr.is_some() { + kmr_common::tag::SecureStorage::Available + } else { + kmr_common::tag::SecureStorage::Unavailable + } + } + + /// Return the device's boot information. + fn boot_info(&self) -> Result<&keymint::BootInfo, Error> { + self.boot_info + .as_ref() + .ok_or_else(|| km_err!(HardwareNotYetAvailable, "no boot info available")) + } + + /// Return a copy of the device's boot information, with the verified boot key + /// hashed (if necessary). + fn boot_info_hashed_key(&self) -> Result { + let mut boot_info = self.boot_info()?.clone(); + if boot_info.verified_boot_key.len() > 32 { + // It looks like we have the actual key, not a hash thereof. Change that. + boot_info.verified_boot_key = + try_to_vec(&self.imp.sha256.hash(&boot_info.verified_boot_key)?)?; + } + Ok(boot_info) + } + + /// Parse and decrypt an encrypted key blob, allowing through keys that require upgrade due to + /// patchlevel updates. Keys that appear to be in a legacy format may still emit a + /// [`ErrorCode::KeyRequiresUpgrade`] error. + fn keyblob_parse_decrypt_backlevel( + &self, + key_blob: &[u8], + params: &[KeyParam], + ) -> Result<(keyblob::PlaintextKeyBlob, Option), Error> { + let encrypted_keyblob = match keyblob::EncryptedKeyBlob::new(key_blob) { + Ok(k) => k, + Err(e) => { + // We might have failed to parse the keyblob because it is in some prior format. + if let Some(old_key) = self.dev.legacy_key.as_ref() { + if old_key.is_legacy_key(key_blob, params, self.boot_info()?) { + return Err(km_err!( + KeyRequiresUpgrade, + "legacy key detected, request upgrade" + )); + } + } + return Err(e); + } + }; + let hidden = tag::hidden(params, self.root_of_trust()?)?; + let sdd_slot = encrypted_keyblob.secure_deletion_slot(); + let root_kek = self.root_kek(encrypted_keyblob.kek_context())?; + let keyblob = keyblob::decrypt( + match &self.dev.sdd_mgr { + None => None, + Some(mr) => Some(&**mr), + }, + &*self.imp.aes, + &*self.imp.hkdf, + &root_kek, + encrypted_keyblob, + hidden, + )?; + Ok((keyblob, sdd_slot)) + } + + /// Parse and decrypt an encrypted key blob, detecting keys that require upgrade. + fn keyblob_parse_decrypt( + &self, + key_blob: &[u8], + params: &[KeyParam], + ) -> Result<(keyblob::PlaintextKeyBlob, Option), Error> { + let (keyblob, slot) = self.keyblob_parse_decrypt_backlevel(key_blob, params)?; + + // Check all of the patchlevels and versions to see if key upgrade is required. + fn check(v: &u32, curr: u32, name: &str) -> Result<(), Error> { + match (*v).cmp(&curr) { + Ordering::Less => Err(km_err!( + KeyRequiresUpgrade, + "keyblob with old {} {} needs upgrade to current {}", + name, + v, + curr + )), + Ordering::Equal => Ok(()), + Ordering::Greater => Err(km_err!( + InvalidKeyBlob, + "keyblob with future {} {} (current {})", + name, + v, + curr + )), + } + } + + let key_chars = keyblob.characteristics_at(self.hw_info.security_level)?; + for param in key_chars { + match param { + KeyParam::OsVersion(v) => { + if let Some(hal_info) = &self.hal_info { + if hal_info.os_version == 0 { + // Special case: upgrades to OS version zero are always allowed. + if *v != 0 { + warn!("requesting upgrade to OS version 0"); + return Err(km_err!( + KeyRequiresUpgrade, + "keyblob with OS version {} needs upgrade to current version 0", + v, + )); + } + } else { + check(v, hal_info.os_version, "OS version")?; + } + } else { + error!("OS version not available, can't check for upgrade from {}", v); + } + } + KeyParam::OsPatchlevel(v) => { + if let Some(hal_info) = &self.hal_info { + check(v, hal_info.os_patchlevel, "OS patchlevel")?; + } else { + error!("OS patchlevel not available, can't check for upgrade from {}", v); + } + } + KeyParam::VendorPatchlevel(v) => { + if let Some(hal_info) = &self.hal_info { + check(v, hal_info.vendor_patchlevel, "vendor patchlevel")?; + } else { + error!( + "vendor patchlevel not available, can't check for upgrade from {}", + v + ); + } + } + KeyParam::BootPatchlevel(v) => { + if let Some(boot_info) = &self.boot_info { + check(v, boot_info.boot_patchlevel, "boot patchlevel")?; + } else { + error!("boot patchlevel not available, can't check for upgrade from {}", v); + } + } + _ => {} + } + } + Ok((keyblob, slot)) + } + + /// Generate a unique identifier for a keyblob. + fn key_id(&self, keyblob: &[u8]) -> Result { + let mut hmac_op = + self.imp.hmac.begin(crypto::hmac::Key(vec_try![0; 16]?).into(), Digest::Sha256)?; + hmac_op.update(keyblob)?; + let tag = hmac_op.finish()?; + + Ok(KeyId(tag.try_into().map_err(|_e| { + km_err!(SecureHwCommunicationFailed, "wrong size output from HMAC-SHA256") + })?)) + } + + /// Increment the use count for the given key ID, failing if `max_uses` is reached. + fn update_use_count(&mut self, key_id: KeyId, max_uses: u32) -> Result<(), Error> { + let mut free_idx = None; + let mut slot_idx = None; + for idx in 0..self.use_count.len() { + match &self.use_count[idx] { + None if free_idx.is_none() => free_idx = Some(idx), + None => {} + Some(UseCount { key_id: k, count: _count }) if *k == key_id => { + slot_idx = Some(idx); + break; + } + Some(_) => {} + } + } + if slot_idx.is_none() { + // First use of this key ID; use a free slot if available. + if let Some(idx) = free_idx { + self.use_count[idx] = Some(UseCount { key_id, count: 0 }); + slot_idx = Some(idx); + } + } + + if let Some(idx) = slot_idx { + let c = self.use_count[idx].as_mut().unwrap(); // safe: code above guarantees + if c.count >= max_uses as u64 { + Err(km_err!(KeyMaxOpsExceeded, "use count {} >= limit {}", c.count, max_uses)) + } else { + c.count += 1; + Ok(()) + } + } else { + Err(km_err!(TooManyOperations, "too many use-counted keys already in play")) + } + } + + /// Configure the boot-specific root of trust info. KeyMint implementors should call this + /// method when this information arrives from the bootloader (which happens in an + /// implementation-specific manner). + pub fn set_boot_info(&mut self, boot_info: keymint::BootInfo) -> Result<(), Error> { + if !self.in_early_boot { + error!("Rejecting attempt to set boot info {:?} after early boot", boot_info); + return Err(km_err!( + EarlyBootEnded, + "attempt to set boot info to {boot_info:?} after early boot" + )); + } + if let Some(existing_boot_info) = &self.boot_info { + if *existing_boot_info == boot_info { + warn!( + "Boot info already set, ignoring second attempt to set same values {:?}", + boot_info + ); + } else { + return Err(km_err!( + RootOfTrustAlreadySet, + "attempt to set boot info to {:?} but already set to {:?}", + boot_info, + existing_boot_info + )); + } + } else { + info!("Setting boot_info to {:?}", boot_info); + let rot_info = RootOfTrustInfo { + verified_boot_key: boot_info.verified_boot_key.clone(), + device_boot_locked: boot_info.device_boot_locked, + verified_boot_state: boot_info.verified_boot_state, + }; + self.boot_info = Some(boot_info); + self.rot_data = + Some(rot_info.into_vec().map_err(|e| { + km_err!(EncodingError, "failed to encode root-of-trust: {:?}", e) + })?); + } + Ok(()) + } + + /// Check if HAL-derived information has been set. This is used as an + /// indication that we are past the boot stage. + pub fn is_hal_info_set(&self) -> bool { + self.hal_info.is_some() + } + + /// Configure the HAL-derived information, learnt from the userspace + /// operating system. + pub fn set_hal_info(&mut self, hal_info: HalInfo) { + if self.hal_info.is_none() { + info!("Setting hal_info to {:?}", hal_info); + self.hal_info = Some(hal_info); + } else { + warn!( + "Hal info already set to {:?}, ignoring new values {:?}", + self.hal_info, hal_info + ); + } + } + + /// Configure the version of the HAL that this TA should act as. + pub fn set_hal_version(&mut self, aidl_version: u32) -> Result<(), Error> { + let aidl_version = match aidl_version { + 100 => KeyMintHalVersion::V1, + 200 => KeyMintHalVersion::V2, + 300 => KeyMintHalVersion::V3, + 400 => KeyMintHalVersion::V4, + _ => return Err(km_err!(InvalidArgument, "unsupported HAL version {}", aidl_version)), + }; + if aidl_version == self.aidl_version { + debug!("Set aidl_version to existing version {aidl_version:?}"); + } else if cfg!(feature = "downgrade") { + info!("Change aidl_version from {:?} to {:?}", self.aidl_version, aidl_version); + self.aidl_version = aidl_version; + } else { + // Only allow HAL-triggered downgrade if the "downgrade" feature is enabled. + warn!( + "Ignoring request to change aidl_version from {:?} to {:?}", + self.aidl_version, aidl_version + ); + } + Ok(()) + } + + /// Configure attestation IDs externally. + pub fn set_attestation_ids(&self, ids: AttestationIdInfo) { + if self.dev.attest_ids.is_some() { + error!("Attempt to set attestation IDs externally"); + } else if self.attestation_id_info.borrow().is_some() { + error!("Attempt to set attestation IDs when already set"); + } else { + warn!("Setting attestation IDs directly"); + *self.attestation_id_info.borrow_mut() = Some(Rc::new(ids)); + } + } + + /// Retrieve the attestation ID information for the device, if available. + fn get_attestation_ids(&self) -> Option> { + if self.attestation_id_info.borrow().is_none() { + if let Some(get_ids_impl) = self.dev.attest_ids.as_ref() { + // Attestation IDs are not populated, but we have a trait implementation that + // may provide them. + match get_ids_impl.get() { + Ok(ids) => *self.attestation_id_info.borrow_mut() = Some(Rc::new(ids)), + Err(e) => error!("Failed to retrieve attestation IDs: {:?}", e), + } + } + } + self.attestation_id_info.borrow().as_ref().cloned() + } + + /// Retrieve the DICE info for the device, if available. + fn get_dice_info(&self) -> Option> { + if self.dice_info.borrow().is_none() { + // DICE info is not populated, but we have a trait method that + // may provide them. + match self.dev.rpc.get_dice_info(rpc::TestMode(false)) { + Ok(dice_info) => *self.dice_info.borrow_mut() = Some(Rc::new(dice_info)), + Err(e) => error!("Failed to retrieve DICE info: {:?}", e), + } + } + self.dice_info.borrow().as_ref().cloned() + } + + /// Process a single serialized request, returning a serialized response. + pub fn process(&mut self, req_data: &[u8]) -> Vec { + let (req_code, rsp) = match PerformOpReq::from_slice(req_data) { + Ok(req) => { + trace!("-> TA: received request {:?}", req.code()); + (Some(req.code()), self.process_req(req)) + } + Err(e) => { + error!("failed to decode CBOR request: {:?}", e); + // We need to report the error to the HAL, but we don't know whether the request was + // for the `IRemotelyProvisionedComponent` or for one of the other HALs, so we don't + // know what numbering space the error codes are expected to be in. Assume the + // shared KeyMint `ErrorCode` space. + (None, error_rsp(ErrorCode::EncodingError as i32)) + } + }; + trace!("<- TA: send response {:?} rc {}", req_code, rsp.error_code); + match rsp.into_vec() { + Ok(rsp_data) => rsp_data, + Err(e) => { + error!("failed to encode CBOR response: {:?}", e); + invalid_cbor_rsp_data().to_vec() + } + } + } + + /// Process a single request, returning a [`PerformOpResponse`]. + /// + /// Select the appropriate method based on the request type, and use the + /// request fields as parameters to the method. In the opposite direction, + /// build a response message from the values returned by the method. + pub fn process_req(&mut self, req: PerformOpReq) -> PerformOpResponse { + match req { + // Internal messages. + PerformOpReq::SetBootInfo(req) => { + let verified_boot_state = match VerifiedBootState::try_from(req.verified_boot_state) + { + Ok(state) => state, + Err(e) => return op_error_rsp(SetBootInfoRequest::CODE, Error::Cbor(e)), + }; + match self.set_boot_info(keymint::BootInfo { + verified_boot_key: req.verified_boot_key, + device_boot_locked: req.device_boot_locked, + verified_boot_state, + verified_boot_hash: req.verified_boot_hash, + boot_patchlevel: req.boot_patchlevel, + }) { + Ok(_) => op_ok_rsp(PerformOpRsp::SetBootInfo(SetBootInfoResponse {})), + Err(e) => op_error_rsp(SetBootInfoRequest::CODE, e), + } + } + PerformOpReq::SetHalInfo(req) => { + self.set_hal_info(HalInfo { + os_version: req.os_version, + os_patchlevel: req.os_patchlevel, + vendor_patchlevel: req.vendor_patchlevel, + }); + op_ok_rsp(PerformOpRsp::SetHalInfo(SetHalInfoResponse {})) + } + PerformOpReq::SetAttestationIds(req) => { + self.set_attestation_ids(req.ids); + op_ok_rsp(PerformOpRsp::SetAttestationIds(SetAttestationIdsResponse {})) + } + PerformOpReq::SetHalVersion(req) => match self.set_hal_version(req.aidl_version) { + Ok(_) => op_ok_rsp(PerformOpRsp::SetHalVersion(SetHalVersionResponse {})), + Err(e) => op_error_rsp(SetHalVersionRequest::CODE, e), + }, + + // ISharedSecret messages. + PerformOpReq::SharedSecretGetSharedSecretParameters(_req) => { + match self.get_shared_secret_params() { + Ok(ret) => op_ok_rsp(PerformOpRsp::SharedSecretGetSharedSecretParameters( + GetSharedSecretParametersResponse { ret }, + )), + Err(e) => op_error_rsp(GetSharedSecretParametersRequest::CODE, e), + } + } + PerformOpReq::SharedSecretComputeSharedSecret(req) => { + match self.compute_shared_secret(&req.params) { + Ok(ret) => op_ok_rsp(PerformOpRsp::SharedSecretComputeSharedSecret( + ComputeSharedSecretResponse { ret }, + )), + Err(e) => op_error_rsp(ComputeSharedSecretRequest::CODE, e), + } + } + + // ISecureClock messages. + PerformOpReq::SecureClockGenerateTimeStamp(req) => { + match self.generate_timestamp(req.challenge) { + Ok(ret) => op_ok_rsp(PerformOpRsp::SecureClockGenerateTimeStamp( + GenerateTimeStampResponse { ret }, + )), + Err(e) => op_error_rsp(GenerateTimeStampRequest::CODE, e), + } + } + + // IKeyMintDevice messages. + PerformOpReq::DeviceGetHardwareInfo(_req) => match self.get_hardware_info() { + Ok(ret) => { + op_ok_rsp(PerformOpRsp::DeviceGetHardwareInfo(GetHardwareInfoResponse { ret })) + } + Err(e) => op_error_rsp(GetHardwareInfoRequest::CODE, e), + }, + PerformOpReq::DeviceAddRngEntropy(req) => match self.add_rng_entropy(&req.data) { + Ok(_ret) => op_ok_rsp(PerformOpRsp::DeviceAddRngEntropy(AddRngEntropyResponse {})), + Err(e) => op_error_rsp(AddRngEntropyRequest::CODE, e), + }, + PerformOpReq::DeviceGenerateKey(req) => { + match self.generate_key(&req.key_params, req.attestation_key) { + Ok(ret) => { + op_ok_rsp(PerformOpRsp::DeviceGenerateKey(GenerateKeyResponse { ret })) + } + Err(e) => op_error_rsp(GenerateKeyRequest::CODE, e), + } + } + PerformOpReq::DeviceImportKey(req) => { + match self.import_key( + &req.key_params, + req.key_format, + &req.key_data, + req.attestation_key, + KeyImport::NonWrapped, + ) { + Ok(ret) => op_ok_rsp(PerformOpRsp::DeviceImportKey(ImportKeyResponse { ret })), + Err(e) => op_error_rsp(ImportKeyRequest::CODE, e), + } + } + PerformOpReq::DeviceImportWrappedKey(req) => { + match self.import_wrapped_key( + &req.wrapped_key_data, + &req.wrapping_key_blob, + &req.masking_key, + &req.unwrapping_params, + req.password_sid, + req.biometric_sid, + ) { + Ok(ret) => { + op_ok_rsp(PerformOpRsp::DeviceImportWrappedKey(ImportWrappedKeyResponse { + ret, + })) + } + Err(e) => op_error_rsp(ImportWrappedKeyRequest::CODE, e), + } + } + PerformOpReq::DeviceUpgradeKey(req) => { + match self.upgrade_key(&req.key_blob_to_upgrade, req.upgrade_params) { + Ok(ret) => { + op_ok_rsp(PerformOpRsp::DeviceUpgradeKey(UpgradeKeyResponse { ret })) + } + Err(e) => op_error_rsp(UpgradeKeyRequest::CODE, e), + } + } + PerformOpReq::DeviceDeleteKey(req) => match self.delete_key(&req.key_blob) { + Ok(_ret) => op_ok_rsp(PerformOpRsp::DeviceDeleteKey(DeleteKeyResponse {})), + Err(e) => op_error_rsp(DeleteKeyRequest::CODE, e), + }, + PerformOpReq::DeviceDeleteAllKeys(_req) => match self.delete_all_keys() { + Ok(_ret) => op_ok_rsp(PerformOpRsp::DeviceDeleteAllKeys(DeleteAllKeysResponse {})), + Err(e) => op_error_rsp(DeleteAllKeysRequest::CODE, e), + }, + PerformOpReq::DeviceDestroyAttestationIds(_req) => match self.destroy_attestation_ids() + { + Ok(_ret) => op_ok_rsp(PerformOpRsp::DeviceDestroyAttestationIds( + DestroyAttestationIdsResponse {}, + )), + Err(e) => op_error_rsp(DestroyAttestationIdsRequest::CODE, e), + }, + PerformOpReq::DeviceBegin(req) => { + match self.begin_operation(req.purpose, &req.key_blob, req.params, req.auth_token) { + Ok(ret) => op_ok_rsp(PerformOpRsp::DeviceBegin(BeginResponse { ret })), + Err(e) => op_error_rsp(BeginRequest::CODE, e), + } + } + PerformOpReq::DeviceEarlyBootEnded(_req) => match self.early_boot_ended() { + Ok(_ret) => { + op_ok_rsp(PerformOpRsp::DeviceEarlyBootEnded(EarlyBootEndedResponse {})) + } + Err(e) => op_error_rsp(EarlyBootEndedRequest::CODE, e), + }, + PerformOpReq::DeviceConvertStorageKeyToEphemeral(req) => { + match self.convert_storage_key_to_ephemeral(&req.storage_key_blob) { + Ok(ret) => op_ok_rsp(PerformOpRsp::DeviceConvertStorageKeyToEphemeral( + ConvertStorageKeyToEphemeralResponse { ret }, + )), + Err(e) => op_error_rsp(ConvertStorageKeyToEphemeralRequest::CODE, e), + } + } + PerformOpReq::DeviceGetKeyCharacteristics(req) => { + match self.get_key_characteristics(&req.key_blob, req.app_id, req.app_data) { + Ok(ret) => op_ok_rsp(PerformOpRsp::DeviceGetKeyCharacteristics( + GetKeyCharacteristicsResponse { ret }, + )), + Err(e) => op_error_rsp(GetKeyCharacteristicsRequest::CODE, e), + } + } + PerformOpReq::GetRootOfTrustChallenge(_req) => match self.get_root_of_trust_challenge() + { + Ok(ret) => op_ok_rsp(PerformOpRsp::GetRootOfTrustChallenge( + GetRootOfTrustChallengeResponse { ret }, + )), + Err(e) => op_error_rsp(GetRootOfTrustChallengeRequest::CODE, e), + }, + PerformOpReq::GetRootOfTrust(req) => match self.get_root_of_trust(&req.challenge) { + Ok(ret) => op_ok_rsp(PerformOpRsp::GetRootOfTrust(GetRootOfTrustResponse { ret })), + Err(e) => op_error_rsp(GetRootOfTrustRequest::CODE, e), + }, + PerformOpReq::SendRootOfTrust(req) => { + match self.send_root_of_trust(&req.root_of_trust) { + Ok(_ret) => { + op_ok_rsp(PerformOpRsp::SendRootOfTrust(SendRootOfTrustResponse {})) + } + Err(e) => op_error_rsp(SendRootOfTrustRequest::CODE, e), + } + } + PerformOpReq::SetAdditionalAttestationInfo(req) => { + match self.set_additional_attestation_info(req.info) { + Ok(_ret) => op_ok_rsp(PerformOpRsp::SetAdditionalAttestationInfo( + SetAdditionalAttestationInfoResponse {}, + )), + Err(e) => op_error_rsp(SetAdditionalAttestationInfoRequest::CODE, e), + } + } + + // IKeyMintOperation messages. + PerformOpReq::OperationUpdateAad(req) => match self.op_update_aad( + OpHandle(req.op_handle), + &req.input, + req.auth_token, + req.timestamp_token, + ) { + Ok(_ret) => op_ok_rsp(PerformOpRsp::OperationUpdateAad(UpdateAadResponse {})), + Err(e) => op_error_rsp(UpdateAadRequest::CODE, e), + }, + PerformOpReq::OperationUpdate(req) => { + match self.op_update( + OpHandle(req.op_handle), + &req.input, + req.auth_token, + req.timestamp_token, + ) { + Ok(ret) => op_ok_rsp(PerformOpRsp::OperationUpdate(UpdateResponse { ret })), + Err(e) => op_error_rsp(UpdateRequest::CODE, e), + } + } + PerformOpReq::OperationFinish(req) => { + match self.op_finish( + OpHandle(req.op_handle), + req.input.as_deref(), + req.signature.as_deref(), + req.auth_token, + req.timestamp_token, + req.confirmation_token.as_deref(), + ) { + Ok(ret) => op_ok_rsp(PerformOpRsp::OperationFinish(FinishResponse { ret })), + Err(e) => op_error_rsp(FinishRequest::CODE, e), + } + } + PerformOpReq::OperationAbort(req) => match self.op_abort(OpHandle(req.op_handle)) { + Ok(_ret) => op_ok_rsp(PerformOpRsp::OperationAbort(AbortResponse {})), + Err(e) => op_error_rsp(AbortRequest::CODE, e), + }, + + // IRemotelyProvisionedComponentOperation messages. + PerformOpReq::RpcGetHardwareInfo(_req) => match self.get_rpc_hardware_info() { + Ok(ret) => { + op_ok_rsp(PerformOpRsp::RpcGetHardwareInfo(GetRpcHardwareInfoResponse { ret })) + } + Err(e) => op_error_rsp(GetRpcHardwareInfoRequest::CODE, e), + }, + PerformOpReq::RpcGenerateEcdsaP256KeyPair(req) => { + match self.generate_ecdsa_p256_keypair(rpc::TestMode(req.test_mode)) { + Ok((pubkey, ret)) => op_ok_rsp(PerformOpRsp::RpcGenerateEcdsaP256KeyPair( + GenerateEcdsaP256KeyPairResponse { maced_public_key: pubkey, ret }, + )), + Err(e) => op_error_rsp(GenerateEcdsaP256KeyPairRequest::CODE, e), + } + } + PerformOpReq::RpcGenerateCertificateRequest(req) => { + match self.generate_cert_req( + rpc::TestMode(req.test_mode), + req.keys_to_sign, + &req.endpoint_encryption_cert_chain, + &req.challenge, + ) { + Ok((device_info, protected_data, ret)) => { + op_ok_rsp(PerformOpRsp::RpcGenerateCertificateRequest( + GenerateCertificateRequestResponse { device_info, protected_data, ret }, + )) + } + Err(e) => op_error_rsp(GenerateCertificateRequestRequest::CODE, e), + } + } + PerformOpReq::RpcGenerateCertificateV2Request(req) => { + match self.generate_cert_req_v2(req.keys_to_sign, &req.challenge) { + Ok(ret) => op_ok_rsp(PerformOpRsp::RpcGenerateCertificateV2Request( + GenerateCertificateRequestV2Response { ret }, + )), + Err(e) => op_error_rsp(GenerateCertificateRequestV2Request::CODE, e), + } + } + } + } + + fn add_rng_entropy(&mut self, data: &[u8]) -> Result<(), Error> { + if data.len() > 2048 { + return Err(km_err!(InvalidInputLength, "entropy size {} too large", data.len())); + }; + + info!("add {} bytes of entropy", data.len()); + self.imp.rng.add_entropy(data); + Ok(()) + } + + fn early_boot_ended(&mut self) -> Result<(), Error> { + info!("early boot ended"); + self.in_early_boot = false; + Ok(()) + } + + fn get_hardware_info(&self) -> Result { + Ok(KeyMintHardwareInfo { + version_number: self.hw_info.version_number, + security_level: self.hw_info.security_level, + key_mint_name: self.hw_info.impl_name.to_string(), + key_mint_author_name: self.hw_info.author_name.to_string(), + timestamp_token_required: self.imp.clock.is_none(), + }) + } + + fn delete_key(&mut self, keyblob: &[u8]) -> Result<(), Error> { + // Parse the keyblob. It cannot be decrypted, because hidden parameters are not available + // (there is no `params` for them to arrive in). + if let Ok(keyblob::EncryptedKeyBlob::V1(encrypted_keyblob)) = + keyblob::EncryptedKeyBlob::new(keyblob) + { + // We have to trust that any secure deletion slot in the keyblob is valid, because the + // key can't be decrypted. + if let (Some(sdd_mgr), Some(slot)) = + (&mut self.dev.sdd_mgr, encrypted_keyblob.secure_deletion_slot) + { + if let Err(e) = sdd_mgr.delete_secret(slot) { + error!("failed to delete secure deletion slot: {:?}", e); + } + } + } else { + // We might have failed to parse the keyblob because it is in some prior format. + if let Some(old_key) = self.dev.legacy_key.as_mut() { + if let Err(e) = old_key.delete_legacy_key(keyblob) { + error!("failed to parse keyblob as legacy : {:?}, ignoring", e); + } + } else { + error!("failed to parse keyblob, ignoring"); + } + } + + Ok(()) + } + + fn delete_all_keys(&mut self) -> Result<(), Error> { + if let Some(sdd_mgr) = &mut self.dev.sdd_mgr { + error!("secure deleting all keys -- device likely to need factory reset!"); + sdd_mgr.delete_all(); + } + Ok(()) + } + + fn destroy_attestation_ids(&mut self) -> Result<(), Error> { + match self.dev.attest_ids.as_mut() { + Some(attest_ids) => { + // Drop any cached copies too. + *self.attestation_id_info.borrow_mut() = None; + error!("destroying all device attestation IDs!"); + attest_ids.destroy_all() + } + None => { + error!("destroying device attestation IDs requested but not supported"); + Err(km_err!(Unimplemented, "no attestation ID functionality available")) + } + } + } + + fn get_root_of_trust_challenge(&mut self) -> Result<[u8; 16], Error> { + if !self.is_strongbox() { + return Err(km_err!(Unimplemented, "root-of-trust challenge only for StrongBox")); + } + self.imp.rng.fill_bytes(&mut self.rot_challenge[..]); + Ok(self.rot_challenge) + } + + fn get_root_of_trust(&mut self, challenge: &[u8]) -> Result, Error> { + if self.is_strongbox() { + return Err(km_err!(Unimplemented, "root-of-trust retrieval not for StrongBox")); + } + let payload = self + .boot_info_hashed_key()? + .to_tagged_vec() + .map_err(|_e| km_err!(EncodingError, "Failed to CBOR-encode RootOfTrust"))?; + + let mac0 = coset::CoseMac0Builder::new() + .protected( + coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::HMAC_256_256).build(), + ) + .payload(payload) + .try_create_tag(challenge, |data| self.device_hmac(data))? + .build(); + mac0.to_tagged_vec() + .map_err(|_e| km_err!(EncodingError, "Failed to CBOR-encode RootOfTrust")) + } + + fn send_root_of_trust(&mut self, root_of_trust: &[u8]) -> Result<(), Error> { + if !self.is_strongbox() { + return Err(km_err!(Unimplemented, "root-of-trust delivery only for StrongBox")); + } + let mac0 = coset::CoseMac0::from_tagged_slice(root_of_trust) + .map_err(|_e| km_err!(InvalidArgument, "Failed to CBOR-decode CoseMac0"))?; + mac0.verify_tag(&self.rot_challenge, |tag, data| { + match self.verify_device_hmac(data, tag) { + Ok(true) => Ok(()), + Ok(false) => { + Err(km_err!(VerificationFailed, "HMAC verification of RootOfTrust failed")) + } + Err(e) => Err(e), + } + })?; + let payload = + mac0.payload.ok_or_else(|| km_err!(InvalidArgument, "Missing payload in CoseMac0"))?; + let boot_info = keymint::BootInfo::from_tagged_slice(&payload) + .map_err(|_e| km_err!(InvalidArgument, "Failed to CBOR-decode RootOfTrust"))?; + if self.boot_info.is_none() { + info!("Setting boot_info to TEE-provided {:?}", boot_info); + self.boot_info = Some(boot_info); + } else { + info!("Ignoring TEE-provided RootOfTrust {:?} as already set", boot_info); + } + Ok(()) + } + + fn set_additional_attestation_info(&mut self, info: Vec) -> Result<(), Error> { + for param in info { + let tag = param.tag(); + if !ALLOWED_ADDITIONAL_ATTESTATION_TAGS.contains(&tag) { + warn!("ignoring non-allowlisted tag: {tag:?}"); + continue; + } + match self.additional_attestation_info.iter().find(|&x| x.tag() == tag) { + Some(value) if value == ¶m => { + warn!( + concat!( + "additional attestation info for: {:?} already set, ignoring repeated", + " attempt to set same info" + ), + param + ); + continue; + } + Some(value) => { + return Err(set_additional_attestation_info_err( + tag, + format!( + concat!( + "attempt to set additional attestation info for: {:?}, but that tag", + " already has a different value set: {:?}" + ), + param, value + ), + )); + } + None => { + self.additional_attestation_info.push(param.clone()); + } + } + } + Ok(()) + } + + fn convert_storage_key_to_ephemeral(&self, keyblob: &[u8]) -> Result, Error> { + if let Some(sk_wrapper) = &self.dev.sk_wrapper { + // Parse and decrypt the keyblob. Note that there is no way to provide extra hidden + // params on the API. + let (keyblob, _) = self.keyblob_parse_decrypt(keyblob, &[])?; + + // Check that the keyblob is indeed a storage key. + let chars = keyblob.characteristics_at(self.hw_info.security_level)?; + if !get_bool_tag_value!(chars, StorageKey)? { + return Err(km_err!(InvalidArgument, "attempting to convert non-storage key")); + } + + // Now that we've got the key material, use a device-specific method to re-wrap it + // with an ephemeral key. + sk_wrapper.ephemeral_wrap(&keyblob.key_material) + } else { + Err(km_err!(Unimplemented, "storage key wrapping unavailable")) + } + } + + fn get_key_characteristics( + &self, + key_blob: &[u8], + app_id: Vec, + app_data: Vec, + ) -> Result, Error> { + // Parse and decrypt the keyblob, which requires extra hidden params. + let mut params = vec_try_with_capacity!(2)?; + if !app_id.is_empty() { + params.push(KeyParam::ApplicationId(app_id)); // capacity enough + } + if !app_data.is_empty() { + params.push(KeyParam::ApplicationData(app_data)); // capacity enough + } + let (keyblob, _) = self.keyblob_parse_decrypt(key_blob, ¶ms)?; + Ok(keyblob.characteristics) + } + + /// Generate an HMAC-SHA256 value over the data using the device's HMAC key (if available). + fn device_hmac(&self, data: &[u8]) -> Result, Error> { + match &self.device_hmac { + Some(traitobj) => traitobj.hmac(&*self.imp.hmac, data), + None => { + error!("HMAC requested but no key available!"); + Err(km_err!(HardwareNotYetAvailable, "HMAC key not agreed")) + } + } + } + + /// Verify an HMAC-SHA256 value over the data using the device's HMAC key (if available). + fn verify_device_hmac(&self, data: &[u8], mac: &[u8]) -> Result { + let remac = self.device_hmac(data)?; + Ok(self.imp.compare.eq(mac, &remac)) + } + + /// Return the root of trust that is bound into keyblobs. + fn root_of_trust(&self) -> Result<&[u8], Error> { + match &self.rot_data { + Some(data) => Ok(data), + None => Err(km_err!(HardwareNotYetAvailable, "No root-of-trust info available")), + } + } + + /// Return the root key used for key encryption. + fn root_kek(&self, context: &[u8]) -> Result, Error> { + self.dev.keys.root_kek(context) + } + + /// Add KeyMint-generated tags to the provided [`KeyCharacteristics`]. + fn add_keymint_tags( + &self, + chars: &mut Vec, + origin: KeyOrigin, + ) -> Result<(), Error> { + for kc in chars { + if kc.security_level == self.hw_info.security_level { + kc.authorizations.try_push(KeyParam::Origin(origin))?; + if let Some(hal_info) = &self.hal_info { + kc.authorizations.try_extend_from_slice(&[ + KeyParam::OsVersion(hal_info.os_version), + KeyParam::OsPatchlevel(hal_info.os_patchlevel), + KeyParam::VendorPatchlevel(hal_info.vendor_patchlevel), + ])?; + } + if let Some(boot_info) = &self.boot_info { + kc.authorizations + .try_push(KeyParam::BootPatchlevel(boot_info.boot_patchlevel))?; + } + return Ok(()); + } + } + Err(km_err!( + InvalidArgument, + "no characteristics at our security level {:?}", + self.hw_info.security_level + )) + } +} + +/// Create an OK response structure with the given inner response message. +fn op_ok_rsp(rsp: PerformOpRsp) -> PerformOpResponse { + // Zero is OK in any context. + PerformOpResponse { error_code: 0, rsp: Some(rsp) } +} + +/// Create a response structure with the given error code. +fn error_rsp(error_code: i32) -> PerformOpResponse { + PerformOpResponse { error_code, rsp: None } +} + +/// Create a response structure with the given error. +fn op_error_rsp(op: KeyMintOperation, err: Error) -> PerformOpResponse { + warn!("failing {:?} request with error {:?}", op, err); + if kmr_wire::is_rpc_operation(op) { + // The IRemotelyProvisionedComponent HAL uses a different error space than the + // other HALs. + let rpc_err: rpc::ErrorCode = match err { + Error::Cbor(_) | Error::Der(_) | Error::Alloc(_) => rpc::ErrorCode::Failed, + Error::Hal(_, _) => { + error!("encountered non-RKP error on RKP method! {:?}", err); + rpc::ErrorCode::Failed + } + Error::Rpc(e, _) => e, + }; + error_rsp(rpc_err as i32) + } else { + let hal_err = match err { + Error::Cbor(_) | Error::Der(_) => ErrorCode::InvalidArgument, + Error::Hal(e, _) => e, + Error::Rpc(_, _) => { + error!("encountered RKP error on non-RKP method! {:?}", err); + ErrorCode::InvalidArgument + } + Error::Alloc(_) => ErrorCode::MemoryAllocationFailed, + }; + error_rsp(hal_err as i32) + } +} + +/// Create an Error for [`KeyMintTa::set_additional_attestation_info`] failure that corresponds to +/// the specified tag. +fn set_additional_attestation_info_err(tag: Tag, err_msg: String) -> Error { + match tag { + Tag::ModuleHash => km_err!(ModuleHashAlreadySet, "{}", err_msg), + _ => km_err!(InvalidTag, "unexpected tag: {tag:?}"), + } +} + +/// Hand-encoded [`PerformOpResponse`] data for [`ErrorCode::UNKNOWN_ERROR`]. +/// Does not perform CBOR serialization (and so is suitable for error reporting if/when +/// CBOR serialization fails). +fn invalid_cbor_rsp_data() -> [u8; 5] { + [ + 0x82, // 2-arr + 0x39, // nint, len 2 + 0x03, // 0x3e7(999) + 0xe7, // = -1000 + 0x80, // 0-arr + ] +} + +/// Build the HMAC input for a [`HardwareAuthToken`] +pub fn hardware_auth_token_mac_input(token: &HardwareAuthToken) -> Result, Error> { + let mut result = vec_try_with_capacity!( + size_of::() + // version=0 (BE) + size_of::() + // challenge (Host) + size_of::() + // user_id (Host) + size_of::() + // authenticator_id (Host) + size_of::() + // authenticator_type (BE) + size_of::() // timestamp (BE) + )?; + result.extend_from_slice(&0u8.to_be_bytes()[..]); + result.extend_from_slice(&token.challenge.to_ne_bytes()[..]); + result.extend_from_slice(&token.user_id.to_ne_bytes()[..]); + result.extend_from_slice(&token.authenticator_id.to_ne_bytes()[..]); + result.extend_from_slice(&(token.authenticator_type as i32).to_be_bytes()[..]); + result.extend_from_slice(&token.timestamp.milliseconds.to_be_bytes()[..]); + Ok(result) +} diff --git a/libs/rust/ta/src/operation.rs b/libs/rust/ta/src/operation.rs new file mode 100644 index 0000000..d3f880c --- /dev/null +++ b/libs/rust/ta/src/operation.rs @@ -0,0 +1,869 @@ +// Copyright 2022, 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. + +//! TA functionality related to in-progress crypto operations. + +use kmr_common::{ + crypto, + crypto::{aes, AadOperation, AccumulatingOperation, EmittingOperation, KeyMaterial}, + get_bool_tag_value, get_opt_tag_value, get_tag_value, keyblob, km_err, tag, try_to_vec, Error, + FallibleAllocExt, +}; +use kmr_wire::{ + keymint::{ErrorCode, HardwareAuthToken, KeyParam, KeyPurpose}, + secureclock::{TimeStampToken, Timestamp}, + InternalBeginResult, +}; +use log::{error, info, warn}; + +/// A trusted confirmation token should be the size of HMAC-SHA256 output. +const CONFIRMATION_TOKEN_SIZE: usize = 32; + +/// Trusted confirmation data prefix, from IConfirmationResultCallback.hal. +const CONFIRMATION_DATA_PREFIX: &[u8] = b"confirmation token"; + +/// Maximum size of messages with `Tag::TrustedConfirmationRequired` set. +/// See +const CONFIRMATION_MESSAGE_MAX_LEN: usize = 6144; + +/// Union holder for in-progress cryptographic operations, each of which is an instance +/// of the relevant trait. +pub(crate) enum CryptoOperation { + Aes(Box), + AesGcm(Box), + Des(Box), + HmacSign(Box, usize), // tag length + HmacVerify(Box, core::ops::Range), + RsaDecrypt(Box), + RsaSign(Box), + EcAgree(Box), + EcSign(Box), +} + +/// Current state of an operation. +pub(crate) struct Operation { + /// Random handle used to identify the operation, also used as a challenge. + pub handle: OpHandle, + + /// Whether update_aad() is allowed (only ever true for AEADs before data has arrived). + pub aad_allowed: bool, + + /// Secure deletion slot to delete on successful completion of the operation. + pub slot_to_delete: Option, + + /// Buffer to accumulate data being signed that must have a trusted confirmation. This + /// data matches what was been fed into `crypto_op`'s `update` method (but has a size + /// limit so will not grow unboundedly). + pub trusted_conf_data: Option>, + + /// Authentication data to check. + pub auth_info: Option, + + pub crypto_op: CryptoOperation, + + /// Accumulated input size. + pub input_size: usize, +} + +impl Operation { + /// Check whether `len` additional bytes of data can be accommodated by the `Operation`. + fn check_size(&mut self, len: usize) -> Result<(), Error> { + self.input_size += len; + let max_size = match &self.crypto_op { + CryptoOperation::HmacSign(op, _) + | CryptoOperation::HmacVerify(op, _) + | CryptoOperation::RsaDecrypt(op) + | CryptoOperation::RsaSign(op) + | CryptoOperation::EcAgree(op) + | CryptoOperation::EcSign(op) => op.max_input_size(), + _ => None, + }; + if let Some(max_size) = max_size { + if self.input_size > max_size { + return Err(km_err!( + InvalidInputLength, + "too much input accumulated for operation" + )); + } + } + Ok(()) + } +} + +/// Newtype for operation handles. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct OpHandle(pub i64); + +/// Authentication requirements associated with an operation. +pub(crate) struct AuthInfo { + secure_ids: Vec, + auth_type: u32, + timeout_secs: Option, +} + +impl AuthInfo { + /// Optionally build an `AuthInfo` from key characteristics. If no authentication is needed on + /// `update()`/`update_aad()`/`finish()`, return `None`. + fn new(key_chars: &[KeyParam]) -> Result, Error> { + let mut secure_ids = Vec::new(); + let mut auth_type = None; + let mut timeout_secs = None; + let mut no_auth_required = false; + + for param in key_chars { + match param { + KeyParam::UserSecureId(sid) => secure_ids.try_push(*sid)?, + KeyParam::UserAuthType(atype) => { + if auth_type.is_none() { + auth_type = Some(*atype); + } else { + return Err(km_err!(InvalidKeyBlob, "duplicate UserAuthType tag found")); + } + } + KeyParam::AuthTimeout(secs) => { + if timeout_secs.is_none() { + timeout_secs = Some(*secs) + } else { + return Err(km_err!(InvalidKeyBlob, "duplicate AuthTimeout tag found")); + } + } + KeyParam::NoAuthRequired => no_auth_required = true, + _ => {} + } + } + + if secure_ids.is_empty() { + Ok(None) + } else if let Some(auth_type) = auth_type { + if no_auth_required { + Err(km_err!(InvalidKeyBlob, "found both NO_AUTH_REQUIRED and USER_SECURE_ID")) + } else { + Ok(Some(AuthInfo { secure_ids, auth_type, timeout_secs })) + } + } else { + Err(km_err!(KeyUserNotAuthenticated, "found USER_SECURE_ID but no USER_AUTH_TYPE")) + } + } +} + +impl crate::KeyMintTa { + pub(crate) fn begin_operation( + &mut self, + purpose: KeyPurpose, + key_blob: &[u8], + params: Vec, + auth_token: Option, + ) -> Result { + let op_idx = self.new_operation_index()?; + + // Parse and decrypt the keyblob, which requires extra hidden params. + let (keyblob, sdd_slot) = self.keyblob_parse_decrypt(key_blob, ¶ms)?; + let keyblob::PlaintextKeyBlob { characteristics, key_material } = keyblob; + + // Validate parameters. + let key_chars = + kmr_common::tag::characteristics_at(&characteristics, self.hw_info.security_level)?; + tag::check_begin_params(key_chars, purpose, ¶ms)?; + self.check_begin_auths(key_chars, key_blob)?; + + let trusted_conf_data = if purpose == KeyPurpose::Sign + && get_bool_tag_value!(key_chars, TrustedConfirmationRequired)? + { + // Trusted confirmation is required; accumulate the signed data in an extra buffer, + // starting with a prefix. + Some(try_to_vec(CONFIRMATION_DATA_PREFIX)?) + } else { + None + }; + + let slot_to_delete = if let Some(&1) = get_opt_tag_value!(key_chars, UsageCountLimit)? { + warn!("single-use key will be deleted on operation completion"); + sdd_slot + } else { + None + }; + + // At most one operation involving proof of user presence can be in-flight at a time. + let presence_required = get_bool_tag_value!(key_chars, TrustedUserPresenceRequired)?; + if presence_required && self.presence_required_op.is_some() { + return Err(km_err!( + ConcurrentProofOfPresenceRequested, + "additional op with proof-of-presence requested" + )); + } + + let mut op_auth_info = AuthInfo::new(key_chars)?; + if let Some(auth_info) = &op_auth_info { + // Authentication checks are required on begin() if there's a timeout that + // we can check. + if let Some(timeout_secs) = auth_info.timeout_secs { + if let Some(clock) = &self.imp.clock { + let now: Timestamp = clock.now().into(); + let auth_token = auth_token.ok_or_else(|| { + km_err!(KeyUserNotAuthenticated, "no auth token on begin()") + })?; + self.check_auth_token( + auth_token, + auth_info, + Some(now), + Some(timeout_secs), + None, + )?; + + // Auth already checked, nothing needed on subsequent calls + op_auth_info = None; + } else if let Some(auth_token) = auth_token { + self.check_auth_token(auth_token, auth_info, None, None, None)?; + } + } + } + + // Re-use the same random value for both: + // - op_handle: the way to identify which operation is involved + // - challenge: the value used as part of the input for authentication tokens + let op_handle = self.new_op_handle(); + let challenge = op_handle.0; + let mut ret_params = Vec::new(); + let op = match key_material { + KeyMaterial::Aes(key) => { + let caller_nonce = get_opt_tag_value!(¶ms, Nonce)?; + let mode = aes::Mode::new(¶ms, caller_nonce, &mut *self.imp.rng)?; + let dir = match purpose { + KeyPurpose::Encrypt => crypto::SymmetricOperation::Encrypt, + KeyPurpose::Decrypt => crypto::SymmetricOperation::Decrypt, + _ => { + return Err(km_err!( + IncompatiblePurpose, + "invalid purpose {:?} for AES key", + purpose + )) + } + }; + if caller_nonce.is_none() { + // Need to return any randomly-generated nonce to the caller. + match &mode { + aes::Mode::Cipher(aes::CipherMode::EcbNoPadding) + | aes::Mode::Cipher(aes::CipherMode::EcbPkcs7Padding) => {} + aes::Mode::Cipher(aes::CipherMode::CbcNoPadding { nonce: n }) + | aes::Mode::Cipher(aes::CipherMode::CbcPkcs7Padding { nonce: n }) => { + ret_params.try_push(KeyParam::Nonce(try_to_vec(n)?))? + } + aes::Mode::Cipher(aes::CipherMode::Ctr { nonce: n }) => { + ret_params.try_push(KeyParam::Nonce(try_to_vec(n)?))? + } + aes::Mode::Aead(aes::GcmMode::GcmTag12 { nonce: n }) + | aes::Mode::Aead(aes::GcmMode::GcmTag13 { nonce: n }) + | aes::Mode::Aead(aes::GcmMode::GcmTag14 { nonce: n }) + | aes::Mode::Aead(aes::GcmMode::GcmTag15 { nonce: n }) + | aes::Mode::Aead(aes::GcmMode::GcmTag16 { nonce: n }) => { + ret_params.try_push(KeyParam::Nonce(try_to_vec(n)?))? + } + } + } + match &mode { + aes::Mode::Cipher(mode) => Operation { + handle: op_handle, + aad_allowed: false, + input_size: 0, + slot_to_delete, + trusted_conf_data, + auth_info: op_auth_info, + crypto_op: CryptoOperation::Aes(self.imp.aes.begin(key, *mode, dir)?), + }, + aes::Mode::Aead(mode) => Operation { + handle: op_handle, + aad_allowed: true, + input_size: 0, + slot_to_delete, + trusted_conf_data, + auth_info: op_auth_info, + crypto_op: CryptoOperation::AesGcm( + self.imp.aes.begin_aead(key, *mode, dir)?, + ), + }, + } + } + KeyMaterial::TripleDes(key) => { + let caller_nonce = get_opt_tag_value!(¶ms, Nonce)?; + let mode = crypto::des::Mode::new(¶ms, caller_nonce, &mut *self.imp.rng)?; + let dir = match purpose { + KeyPurpose::Encrypt => crypto::SymmetricOperation::Encrypt, + KeyPurpose::Decrypt => crypto::SymmetricOperation::Decrypt, + _ => { + return Err(km_err!( + IncompatiblePurpose, + "invalid purpose {:?} for DES key", + purpose + )) + } + }; + if caller_nonce.is_none() { + // Need to return any randomly-generated nonce to the caller. + match &mode { + crypto::des::Mode::EcbNoPadding | crypto::des::Mode::EcbPkcs7Padding => {} + crypto::des::Mode::CbcNoPadding { nonce: n } + | crypto::des::Mode::CbcPkcs7Padding { nonce: n } => { + ret_params.try_push(KeyParam::Nonce(try_to_vec(n)?))? + } + } + } + Operation { + handle: op_handle, + aad_allowed: false, + input_size: 0, + slot_to_delete, + trusted_conf_data, + auth_info: op_auth_info, + crypto_op: CryptoOperation::Des(self.imp.des.begin(key, mode, dir)?), + } + } + KeyMaterial::Hmac(key) => { + let digest = tag::get_digest(¶ms)?; + + Operation { + handle: op_handle, + aad_allowed: false, + input_size: 0, + slot_to_delete, + trusted_conf_data, + auth_info: op_auth_info, + crypto_op: match purpose { + KeyPurpose::Sign => { + let tag_len = + get_tag_value!(¶ms, MacLength, ErrorCode::MissingMacLength)? + as usize + / 8; + CryptoOperation::HmacSign(self.imp.hmac.begin(key, digest)?, tag_len) + } + KeyPurpose::Verify => { + // Remember the acceptable tag lengths. + let min_tag_len = get_tag_value!( + key_chars, + MinMacLength, + ErrorCode::MissingMinMacLength + )? as usize + / 8; + let max_tag_len = kmr_common::tag::digest_len(digest)? as usize; + CryptoOperation::HmacVerify( + self.imp.hmac.begin(key, digest)?, + min_tag_len..max_tag_len, + ) + } + _ => { + return Err(km_err!( + IncompatiblePurpose, + "invalid purpose {:?} for HMAC key", + purpose + )) + } + }, + } + } + KeyMaterial::Rsa(key) => Operation { + handle: op_handle, + aad_allowed: false, + input_size: 0, + slot_to_delete, + trusted_conf_data, + auth_info: op_auth_info, + crypto_op: match purpose { + KeyPurpose::Decrypt => { + let mode = crypto::rsa::DecryptionMode::new(¶ms)?; + CryptoOperation::RsaDecrypt(self.imp.rsa.begin_decrypt(key, mode)?) + } + KeyPurpose::Sign => { + let mode = crypto::rsa::SignMode::new(¶ms)?; + CryptoOperation::RsaSign(self.imp.rsa.begin_sign(key, mode)?) + } + _ => { + return Err(km_err!( + IncompatiblePurpose, + "invalid purpose {:?} for RSA key", + purpose + )) + } + }, + }, + KeyMaterial::Ec(_, _, key) => Operation { + handle: op_handle, + aad_allowed: false, + input_size: 0, + slot_to_delete, + trusted_conf_data, + auth_info: op_auth_info, + crypto_op: match purpose { + KeyPurpose::AgreeKey => CryptoOperation::EcAgree(self.imp.ec.begin_agree(key)?), + KeyPurpose::Sign => { + let digest = tag::get_digest(¶ms)?; + CryptoOperation::EcSign(self.imp.ec.begin_sign(key, digest)?) + } + _ => { + return Err(km_err!( + IncompatiblePurpose, + "invalid purpose {:?} for EC key", + purpose + )) + } + }, + }, + }; + self.operations[op_idx] = Some(op); + if presence_required { + info!("this operation requires proof-of-presence"); + self.presence_required_op = Some(op_handle); + } + Ok(InternalBeginResult { challenge, params: ret_params, op_handle: op_handle.0 }) + } + + pub(crate) fn op_update_aad( + &mut self, + op_handle: OpHandle, + data: &[u8], + auth_token: Option, + timestamp_token: Option, + ) -> Result<(), Error> { + self.with_authed_operation(op_handle, auth_token, timestamp_token, |op| { + if !op.aad_allowed { + return Err(km_err!(InvalidTag, "update-aad not allowed")); + } + match &mut op.crypto_op { + CryptoOperation::AesGcm(op) => op.update_aad(data), + _ => Err(km_err!(InvalidOperation, "operation does not support update_aad")), + } + }) + } + + pub(crate) fn op_update( + &mut self, + op_handle: OpHandle, + data: &[u8], + auth_token: Option, + timestamp_token: Option, + ) -> Result, Error> { + let check_presence = if self.presence_required_op == Some(op_handle) { + self.presence_required_op = None; + true + } else { + false + }; + let tup_available = self.dev.tup.available(); + self.with_authed_operation(op_handle, auth_token, timestamp_token, |op| { + if check_presence && !tup_available { + return Err(km_err!( + ProofOfPresenceRequired, + "trusted proof of presence required but not available" + )); + } + if let Some(trusted_conf_data) = &mut op.trusted_conf_data { + if trusted_conf_data.len() + data.len() + > CONFIRMATION_DATA_PREFIX.len() + CONFIRMATION_MESSAGE_MAX_LEN + { + return Err(km_err!( + InvalidArgument, + "trusted confirmation data of size {} + {} too big", + trusted_conf_data.len(), + data.len() + )); + } + trusted_conf_data.try_extend_from_slice(data)?; + } + op.aad_allowed = false; + op.check_size(data.len())?; + match &mut op.crypto_op { + CryptoOperation::Aes(op) => op.update(data), + CryptoOperation::AesGcm(op) => op.update(data), + CryptoOperation::Des(op) => op.update(data), + CryptoOperation::HmacSign(op, _) | CryptoOperation::HmacVerify(op, _) => { + op.update(data)?; + Ok(Vec::new()) + } + CryptoOperation::RsaDecrypt(op) => { + op.update(data)?; + Ok(Vec::new()) + } + CryptoOperation::RsaSign(op) => { + op.update(data)?; + Ok(Vec::new()) + } + CryptoOperation::EcAgree(op) => { + op.update(data)?; + Ok(Vec::new()) + } + CryptoOperation::EcSign(op) => { + op.update(data)?; + Ok(Vec::new()) + } + } + }) + } + + pub(crate) fn op_finish( + &mut self, + op_handle: OpHandle, + data: Option<&[u8]>, + signature: Option<&[u8]>, + auth_token: Option, + timestamp_token: Option, + confirmation_token: Option<&[u8]>, + ) -> Result, Error> { + let mut op = self.take_operation(op_handle)?; + self.check_subsequent_auth(&op, auth_token, timestamp_token)?; + + if self.presence_required_op == Some(op_handle) { + self.presence_required_op = None; + if !self.dev.tup.available() { + return Err(km_err!( + ProofOfPresenceRequired, + "trusted proof of presence required but not available" + )); + } + } + if let (Some(trusted_conf_data), Some(data)) = (&mut op.trusted_conf_data, data) { + if trusted_conf_data.len() + data.len() + > CONFIRMATION_DATA_PREFIX.len() + CONFIRMATION_MESSAGE_MAX_LEN + { + return Err(km_err!( + InvalidArgument, + "data of size {} + {} too big", + trusted_conf_data.len(), + data.len() + )); + } + trusted_conf_data.try_extend_from_slice(data)?; + } + + op.check_size(data.map_or(0, |v| v.len()))?; + let result = match op.crypto_op { + CryptoOperation::Aes(mut op) => { + let mut result = if let Some(data) = data { op.update(data)? } else { Vec::new() }; + result.try_extend_from_slice(&op.finish()?)?; + Ok(result) + } + CryptoOperation::AesGcm(mut op) => { + let mut result = if let Some(data) = data { op.update(data)? } else { Vec::new() }; + result.try_extend_from_slice(&op.finish()?)?; + Ok(result) + } + CryptoOperation::Des(mut op) => { + let mut result = if let Some(data) = data { op.update(data)? } else { Vec::new() }; + result.try_extend_from_slice(&op.finish()?)?; + Ok(result) + } + CryptoOperation::HmacSign(mut op, tag_len) => { + if let Some(data) = data { + op.update(data)?; + }; + let mut tag = op.finish()?; + tag.truncate(tag_len); + Ok(tag) + } + CryptoOperation::HmacVerify(mut op, tag_len_range) => { + let sig = signature + .ok_or_else(|| km_err!(InvalidArgument, "signature missing for HMAC verify"))?; + if !tag_len_range.contains(&sig.len()) { + return Err(km_err!( + InvalidArgument, + "signature length invalid: {} not in {:?}", + sig.len(), + tag_len_range + )); + } + + if let Some(data) = data { + op.update(data)?; + }; + let got = op.finish()?; + + if self.imp.compare.eq(&got[..sig.len()], sig) { + Ok(Vec::new()) + } else { + Err(km_err!(VerificationFailed, "HMAC verify failed")) + } + } + CryptoOperation::RsaDecrypt(mut op) => { + if let Some(data) = data { + op.update(data)?; + }; + op.finish() + } + CryptoOperation::RsaSign(mut op) => { + if let Some(data) = data { + op.update(data)?; + }; + op.finish() + } + CryptoOperation::EcAgree(mut op) => { + if let Some(data) = data { + op.update(data)?; + }; + op.finish() + } + CryptoOperation::EcSign(mut op) => { + if let Some(data) = data { + op.update(data)?; + }; + op.finish() + } + }; + if result.is_ok() { + if let Some(trusted_conf_data) = op.trusted_conf_data { + // Accumulated input must be checked against the trusted confirmation token. + self.verify_confirmation_token(&trusted_conf_data, confirmation_token)?; + } + if let (Some(slot), Some(sdd_mgr)) = (op.slot_to_delete, &mut self.dev.sdd_mgr) { + // A successful use of a key with UsageCountLimit(1) triggers deletion. + warn!("Deleting single-use key after use"); + if let Err(e) = sdd_mgr.delete_secret(slot) { + error!("Failed to delete single-use key after use: {:?}", e); + } + } + } + result + } + + pub(crate) fn op_abort(&mut self, op_handle: OpHandle) -> Result<(), Error> { + if self.presence_required_op == Some(op_handle) { + self.presence_required_op = None; + } + let _op = self.take_operation(op_handle)?; + Ok(()) + } + + /// Check TA-specific key authorizations on `begin()`. + fn check_begin_auths(&mut self, key_chars: &[KeyParam], key_blob: &[u8]) -> Result<(), Error> { + if self.dev.bootloader.done() && get_bool_tag_value!(key_chars, BootloaderOnly)? { + return Err(km_err!( + InvalidKeyBlob, + "attempt to use bootloader-only key after bootloader done" + )); + } + if !self.in_early_boot && get_bool_tag_value!(key_chars, EarlyBootOnly)? { + return Err(km_err!(EarlyBootEnded, "attempt to use EARLY_BOOT key after early boot")); + } + + if let Some(max_uses) = get_opt_tag_value!(key_chars, MaxUsesPerBoot)? { + // Track the use count for this key. + let key_id = self.key_id(key_blob)?; + self.update_use_count(key_id, *max_uses)?; + } + Ok(()) + } + + /// Validate a `[keymint::HardwareAuthToken`]. + fn check_auth_token( + &self, + auth_token: HardwareAuthToken, + auth_info: &AuthInfo, + now: Option, + timeout_secs: Option, + challenge: Option, + ) -> Result<(), Error> { + // Common check: confirm the HMAC tag in the token is valid. + let mac_input = crate::hardware_auth_token_mac_input(&auth_token)?; + if !self.verify_device_hmac(&mac_input, &auth_token.mac)? { + return Err(km_err!(KeyUserNotAuthenticated, "failed to authenticate auth_token")); + } + // Common check: token's auth type should match key's USER_AUTH_TYPE. + if (auth_token.authenticator_type as u32 & auth_info.auth_type) == 0 { + return Err(km_err!( + KeyUserNotAuthenticated, + "token auth type {:?} doesn't overlap with key auth type {:?}", + auth_token.authenticator_type, + auth_info.auth_type, + )); + } + + // Common check: token's authenticator or user ID should match key's USER_SECURE_ID. + if !auth_info.secure_ids.iter().any(|sid| { + auth_token.user_id == *sid as i64 || auth_token.authenticator_id == *sid as i64 + }) { + return Err(km_err!( + KeyUserNotAuthenticated, + "neither user id {:?} nor authenticator id {:?} matches key", + auth_token.user_id, + auth_token.authenticator_id + )); + } + + // Optional check: token is in time range. + if let (Some(now), Some(timeout_secs)) = (now, timeout_secs) { + if now.milliseconds > auth_token.timestamp.milliseconds + 1000 * timeout_secs as i64 { + return Err(km_err!( + KeyUserNotAuthenticated, + "now {:?} is later than auth token time {:?} + {} seconds", + now, + auth_token.timestamp, + timeout_secs, + )); + } + } + + // Optional check: challenge matches. + if let Some(challenge) = challenge { + if auth_token.challenge != challenge { + return Err(km_err!(KeyUserNotAuthenticated, "challenge mismatch")); + } + } + Ok(()) + } + + /// Verify that an optional confirmation token matches the provided `data`. + fn verify_confirmation_token(&self, data: &[u8], token: Option<&[u8]>) -> Result<(), Error> { + if let Some(token) = token { + if token.len() != CONFIRMATION_TOKEN_SIZE { + return Err(km_err!( + InvalidArgument, + "confirmation token wrong length {}", + token.len() + )); + } + if self.verify_device_hmac(data, token).map_err(|e| { + km_err!(VerificationFailed, "failed to perform HMAC on confirmation token: {:?}", e) + })? { + Ok(()) + } else { + Err(km_err!(NoUserConfirmation, "trusted confirmation token did not match")) + } + } else { + Err(km_err!(NoUserConfirmation, "no trusted confirmation token provided")) + } + } + + /// Return the index of a free slot in the operations table. + fn new_operation_index(&mut self) -> Result { + self.operations.iter().position(Option::is_none).ok_or_else(|| { + km_err!(TooManyOperations, "current op count {} >= limit", self.operations.len()) + }) + } + + /// Return a new operation handle value that is not currently in use in the + /// operations table. + fn new_op_handle(&mut self) -> OpHandle { + loop { + let op_handle = OpHandle(self.imp.rng.next_u64() as i64); + if self.op_index(op_handle).is_err() { + return op_handle; + } + // op_handle already in use, go around again. + } + } + + /// Return the index into the operations table of an operation identified by `op_handle`. + fn op_index(&self, op_handle: OpHandle) -> Result { + self.operations + .iter() + .position(|op| match op { + Some(op) if op.handle == op_handle => true, + Some(_op) => false, + None => false, + }) + .ok_or_else(|| km_err!(InvalidOperation, "operation handle {:?} not found", op_handle)) + } + + /// Execute the provided lambda over the associated [`Operation`], handling + /// errors. + fn with_authed_operation( + &mut self, + op_handle: OpHandle, + auth_token: Option, + timestamp_token: Option, + f: F, + ) -> Result + where + F: FnOnce(&mut Operation) -> Result, + { + let op_idx = self.op_index(op_handle)?; + let check_again = self.check_subsequent_auth( + self.operations[op_idx].as_ref().unwrap(/* safe: op_index() checks */ ), + auth_token, + timestamp_token, + )?; + let op = self.operations[op_idx].as_mut().unwrap(/* safe: op_index() checks */); + if !check_again { + op.auth_info = None; + } + let result = f(op); + if result.is_err() { + // A failure destroys the operation. + if self.presence_required_op == Some(op_handle) { + self.presence_required_op = None; + } + self.operations[op_idx] = None; + } + result + } + + /// Return the associated [`Operation`], removing it. + fn take_operation(&mut self, op_handle: OpHandle) -> Result { + let op_idx = self.op_index(op_handle)?; + Ok(self.operations[op_idx].take().unwrap(/* safe: op_index() checks */)) + } + + /// Check authentication for an operation that has already begun. Returns an indication as to + /// whether future invocations also need to check authentication. + fn check_subsequent_auth( + &self, + op: &Operation, + auth_token: Option, + timestamp_token: Option, + ) -> Result { + if let Some(auth_info) = &op.auth_info { + let auth_token = auth_token.ok_or_else(|| { + km_err!(KeyUserNotAuthenticated, "no auth token on subsequent op") + })?; + + // Most auth checks happen on begin(), but there are two exceptions. + // a) There is no AUTH_TIMEOUT: there should be a valid auth token on every invocation. + // b) There is an AUTH_TIMEOUT but we have no clock: the first invocation on the + // operation (after `begin()`) should check the timeout, based on a provided + // timestamp token. + if let Some(timeout_secs) = auth_info.timeout_secs { + if self.imp.clock.is_some() { + return Err(km_err!( + InvalidAuthorizationTimeout, + "attempt to check auth timeout after begin() on device with clock!" + )); + } + + // Check that the timestamp token is valid. + let timestamp_token = timestamp_token + .ok_or_else(|| km_err!(InvalidArgument, "no timestamp token provided"))?; + if timestamp_token.challenge != op.handle.0 { + return Err(km_err!(InvalidArgument, "timestamp challenge mismatch")); + } + let mac_input = crate::clock::timestamp_token_mac_input(×tamp_token)?; + if !self.verify_device_hmac(&mac_input, ×tamp_token.mac)? { + return Err(km_err!(InvalidArgument, "timestamp MAC not verified")); + } + + self.check_auth_token( + auth_token, + auth_info, + Some(timestamp_token.timestamp), + Some(timeout_secs), + Some(op.handle.0), + )?; + + // No need to check again. + Ok(false) + } else { + self.check_auth_token(auth_token, auth_info, None, None, Some(op.handle.0))?; + // Check on every invocation + Ok(true) + } + } else { + Ok(false) + } + } +} diff --git a/libs/rust/ta/src/rkp.rs b/libs/rust/ta/src/rkp.rs new file mode 100644 index 0000000..6fb8ba2 --- /dev/null +++ b/libs/rust/ta/src/rkp.rs @@ -0,0 +1,335 @@ +// Copyright 2022, 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. + +//! Functionality for remote key provisioning + +use super::KeyMintTa; +use crate::coset::{ + cbor::value::Value, iana, AsCborValue, CborSerializable, CoseKey, CoseMac0, CoseMac0Builder, + HeaderBuilder, Label, +}; +use crate::RpcInfo; +use kmr_common::crypto::{ + ec::{CoseKeyPurpose, RKP_TEST_KEY_CBOR_MARKER}, + hmac_sha256, KeyMaterial, +}; +use kmr_common::{keyblob, km_err, rpc_err, try_to_vec, Error, FallibleAllocExt}; +use kmr_wire::{ + cbor, + cbor::cbor, + keymint::{ + Algorithm, Digest, EcCurve, KeyParam, KeyPurpose, SecurityLevel, VerifiedBootState, + UNDEFINED_NOT_AFTER, UNDEFINED_NOT_BEFORE, + }, + read_to_value, rpc, + rpc::{ + DeviceInfo, EekCurve, HardwareInfo, MacedPublicKey, ProtectedData, + MINIMUM_SUPPORTED_KEYS_IN_CSR, + }, + rpc::{AUTH_REQ_SCHEMA_V1, CERT_TYPE_KEYMINT, IRPC_V2, IRPC_V3}, + types::KeySizeInBits, + CborError, +}; + +const RPC_P256_KEYGEN_PARAMS: [KeyParam; 8] = [ + KeyParam::Purpose(KeyPurpose::AttestKey), + KeyParam::Algorithm(Algorithm::Ec), + KeyParam::KeySize(KeySizeInBits(256)), + KeyParam::EcCurve(EcCurve::P256), + KeyParam::NoAuthRequired, + KeyParam::Digest(Digest::Sha256), + KeyParam::CertificateNotBefore(UNDEFINED_NOT_BEFORE), + KeyParam::CertificateNotAfter(UNDEFINED_NOT_AFTER), +]; + +const MAX_CHALLENGE_SIZE_V2: usize = 64; + +impl KeyMintTa { + /// Return the UDS certs for the device, encoded in CBOR as per `AdditionalDKSignatures` + /// structure in ProtectedData.aidl for IRPC HAL version 2 and as per `UdsCerts` structure in + /// IRPC HAL version 3. + pub fn uds_certs(&self) -> Result, Error> { + let dice_info = + self.get_dice_info().ok_or_else(|| rpc_err!(Failed, "DICE info not available."))?; + try_to_vec(&dice_info.pub_dice_artifacts.uds_certs) + } + + /// Return the CBOR-encoded `DeviceInfo`. + pub fn rpc_device_info(&self) -> Result, Error> { + let info = self.rpc_device_info_cbor()?; + serialize_cbor(&info) + } + + fn rpc_device_info_cbor(&self) -> Result { + // First make sure all the relevant info is available. + let ids = self.get_attestation_ids().ok_or_else(|| { + km_err!(AttestationIdsNotProvisioned, "attestation ID info not available") + })?; + let boot_info = self + .boot_info + .as_ref() + .ok_or_else(|| km_err!(HardwareNotYetAvailable, "boot info not available"))?; + let hal_info = self + .hal_info + .as_ref() + .ok_or_else(|| km_err!(HardwareNotYetAvailable, "HAL info not available"))?; + + let brand = String::from_utf8_lossy(&ids.brand); + let manufacturer = String::from_utf8_lossy(&ids.manufacturer); + let product = String::from_utf8_lossy(&ids.product); + let model = String::from_utf8_lossy(&ids.model); + let device = String::from_utf8_lossy(&ids.device); + + let bootloader_state = if boot_info.device_boot_locked { "locked" } else { "unlocked" }; + let vbmeta_digest = cbor::value::Value::Bytes(try_to_vec(&boot_info.verified_boot_hash)?); + let vb_state = match boot_info.verified_boot_state { + VerifiedBootState::Verified => "green", + VerifiedBootState::SelfSigned => "yellow", + VerifiedBootState::Unverified => "orange", + VerifiedBootState::Failed => "red", + }; + let security_level = match self.hw_info.security_level { + SecurityLevel::TrustedEnvironment => "tee", + SecurityLevel::Strongbox => "strongbox", + l => { + return Err(km_err!( + HardwareTypeUnavailable, + "security level {:?} not supported", + l + )) + } + }; + + let fused = match &self.rpc_info { + RpcInfo::V2(rpc_info_v2) => rpc_info_v2.fused, + RpcInfo::V3(rpc_info_v3) => rpc_info_v3.fused, + }; + // The DeviceInfo.aidl file specifies that map keys should be ordered according + // to RFC 7049 canonicalization rules, which are: + // - shorter-encoded key < longer-encoded key + // - lexicographic comparison for same-length keys + // Note that this is *different* than the ordering required in RFC 8949 s4.2.1. + let info = cbor!({ + "brand" => brand, + "fused" => i32::from(fused), + "model" => model, + "device" => device, + "product" => product, + "vb_state" => vb_state, + "os_version" => hal_info.os_version.to_string(), + "manufacturer" => manufacturer, + "vbmeta_digest" => vbmeta_digest, + "security_level" => security_level, + "boot_patch_level" => boot_info.boot_patchlevel, + "bootloader_state" => bootloader_state, + "system_patch_level" => hal_info.os_patchlevel, + "vendor_patch_level" => hal_info.vendor_patchlevel, + })?; + Ok(info) + } + + pub(crate) fn get_rpc_hardware_info(&self) -> Result { + match &self.rpc_info { + RpcInfo::V2(rpc_info_v2) => Ok(HardwareInfo { + version_number: IRPC_V2, + rpc_author_name: rpc_info_v2.author_name.to_string(), + supported_eek_curve: rpc_info_v2.supported_eek_curve, + unique_id: Some(rpc_info_v2.unique_id.to_string()), + supported_num_keys_in_csr: MINIMUM_SUPPORTED_KEYS_IN_CSR, + }), + RpcInfo::V3(rpc_info_v3) => Ok(HardwareInfo { + version_number: IRPC_V3, + rpc_author_name: rpc_info_v3.author_name.to_string(), + supported_eek_curve: EekCurve::None, + unique_id: Some(rpc_info_v3.unique_id.to_string()), + supported_num_keys_in_csr: rpc_info_v3.supported_num_of_keys_in_csr, + }), + } + } + + pub(crate) fn generate_ecdsa_p256_keypair( + &mut self, + test_mode: rpc::TestMode, + ) -> Result<(MacedPublicKey, Vec), Error> { + if self.rpc_info.get_version() > IRPC_V2 && test_mode == rpc::TestMode(true) { + return Err(rpc_err!( + Removed, + "generate_ecdsa_p256_keypair does not support test mode in IRPC V3+ HAL." + )); + } + + let (key_material, chars) = self.generate_key_material(&RPC_P256_KEYGEN_PARAMS)?; + + let pub_cose_key = match key_material { + KeyMaterial::Ec(curve, curve_type, ref key) => key.public_cose_key( + &*self.imp.ec, + curve, + curve_type, + CoseKeyPurpose::Sign, + None, + test_mode, + )?, + _ => return Err(km_err!(InvalidKeyBlob, "expected key material of type variant EC.")), + }; + let pub_cose_key_encoded = pub_cose_key.to_vec().map_err(CborError::from)?; + let maced_pub_key = + build_maced_pub_key(pub_cose_key_encoded, |data| -> Result, Error> { + // In test mode, use an all-zero HMAC key. + if test_mode == rpc::TestMode(true) { + return hmac_sha256(&*self.imp.hmac, &[0; 32], data); + } + self.dev.rpc.compute_hmac_sha256(&*self.imp.hmac, &*self.imp.hkdf, data) + })?; + + let key_result = self.finish_keyblob_creation( + &RPC_P256_KEYGEN_PARAMS, + None, + chars, + key_material, + keyblob::SlotPurpose::KeyGeneration, + )?; + + Ok((MacedPublicKey { maced_key: maced_pub_key }, key_result.key_blob)) + } + + pub(crate) fn generate_cert_req( + &self, + _test_mode: rpc::TestMode, + _keys_to_sign: Vec, + _eek_chain: &[u8], + _challenge: &[u8], + ) -> Result<(DeviceInfo, ProtectedData, Vec), Error> { + if self.rpc_info.get_version() > IRPC_V2 { + return Err(rpc_err!(Removed, "generate_cert_req is not supported in IRPC V3+ HAL.")); + } + let _device_info = self.rpc_device_info()?; + Err(km_err!(Unimplemented, "GenerateCertificateRequest is only required for RKP before v3")) + } + + pub(crate) fn generate_cert_req_v2( + &self, + keys_to_sign: Vec, + challenge: &[u8], + ) -> Result, Error> { + if self.rpc_info.get_version() < IRPC_V3 { + return Err(km_err!( + Unimplemented, + "generate_cert_req_v2 is not implemented for IRPC HAL V2 and below." + )); + } + if challenge.len() > MAX_CHALLENGE_SIZE_V2 { + return Err(km_err!( + InvalidArgument, + "Challenge is too big. Actual: {:?}. Maximum: {:?}.", + challenge.len(), + MAX_CHALLENGE_SIZE_V2 + )); + } + // Validate mac and extract the public keys to sign from the MacedPublicKeys + let mut pub_cose_keys: Vec = Vec::new(); + for key_to_sign in keys_to_sign { + let maced_pub_key = key_to_sign.maced_key; + let cose_mac0 = CoseMac0::from_slice(&maced_pub_key).map_err(CborError::from)?; + // Decode the public cose key from payload and check for test keys in production. + // TODO: if implementing IRPC V2, create a helper function to check for test keys that + // takes an indication of whether test mode is allowed + if let Some(pub_cose_key_data) = &cose_mac0.payload { + let pub_cose_key_cbor = read_to_value(pub_cose_key_data)?; + let pub_cose_key = + CoseKey::from_cbor_value(pub_cose_key_cbor.clone()).map_err(CborError::from)?; + let params = pub_cose_key.params; + for param in params { + if param.0 == Label::Int(RKP_TEST_KEY_CBOR_MARKER) { + return Err(rpc_err!( + TestKeyInProductionRequest, + "test key found in the request for generating CSR IRPC V3" + )); + } + } + pub_cose_keys.try_push(pub_cose_key_cbor)?; + } else { + return Err(rpc_err!(Failed, "no payload found in a MacedPublicKey")); + } + + cose_mac0.verify_tag(&[], |expected_tag, data| -> Result<(), Error> { + let computed_tag = + self.dev.rpc.compute_hmac_sha256(&*self.imp.hmac, &*self.imp.hkdf, data)?; + if self.imp.compare.eq(expected_tag, &computed_tag) { + Ok(()) + } else { + Err(rpc_err!(InvalidMac, "invalid tag found in a MacedPublicKey")) + } + })?; + } + // Construct the `CsrPayload` + let rpc_device_info = self.rpc_device_info_cbor()?; + let csr_payload = cbor!([ + Value::Integer(self.rpc_info.get_version().into()), + Value::Text(String::from(CERT_TYPE_KEYMINT)), + rpc_device_info, + Value::Array(pub_cose_keys), + ])?; + let csr_payload_data = serialize_cbor(&csr_payload)?; + // Construct the payload for `SignedData` + let signed_data_payload = + cbor!([Value::Bytes(challenge.to_vec()), Value::Bytes(csr_payload_data)])?; + let signed_data_payload_data = serialize_cbor(&signed_data_payload)?; + + // Process DICE info. + let dice_info = + self.get_dice_info().ok_or_else(|| rpc_err!(Failed, "DICE info not available."))?; + let uds_certs = read_to_value(&dice_info.pub_dice_artifacts.uds_certs)?; + let dice_cert_chain = read_to_value(&dice_info.pub_dice_artifacts.dice_cert_chain)?; + + // Get `SignedData` + let signed_data_cbor = read_to_value(&self.dev.rpc.sign_data_in_cose_sign1( + &*self.imp.ec, + &dice_info.signing_algorithm, + &signed_data_payload_data, + &[], + None, + )?)?; + + // Construct `AuthenticatedRequest` + let authn_req = cbor!([ + Value::Integer(AUTH_REQ_SCHEMA_V1.into()), + uds_certs, + dice_cert_chain, + signed_data_cbor, + ])?; + serialize_cbor(&authn_req) + } +} + +/// Helper function to construct `MacedPublicKey` in MacedPublicKey.aidl +fn build_maced_pub_key(pub_cose_key: Vec, compute_mac: F) -> Result, Error> +where + F: FnOnce(&[u8]) -> Result, Error>, +{ + let protected = HeaderBuilder::new().algorithm(iana::Algorithm::HMAC_256_256).build(); + let cose_mac_0 = CoseMac0Builder::new() + .protected(protected) + .payload(pub_cose_key) + .try_create_tag(&[], compute_mac)? + .build(); + Ok(cose_mac_0.to_vec().map_err(CborError::from)?) +} + +/// Helper function to serialize a `cbor::value::Value` into bytes. +pub fn serialize_cbor(cbor_value: &Value) -> Result, Error> { + let mut buf = Vec::new(); + cbor::ser::into_writer(cbor_value, &mut buf) + .map_err(|_e| Error::Cbor(CborError::EncodeFailed))?; + Ok(buf) +} diff --git a/libs/rust/ta/src/secret.rs b/libs/rust/ta/src/secret.rs new file mode 100644 index 0000000..d35fe92 --- /dev/null +++ b/libs/rust/ta/src/secret.rs @@ -0,0 +1,99 @@ +// Copyright 2022, 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. + +//! TA functionality for shared secret negotiation. + +use crate::device::DeviceHmac; +use kmr_common::{crypto, crypto::hmac, km_err, vec_try, Error, FallibleAllocExt}; +use kmr_wire::{keymint::Digest, sharedsecret::SharedSecretParameters}; +use log::info; + +impl crate::KeyMintTa { + pub(crate) fn get_shared_secret_params(&mut self) -> Result { + if self.shared_secret_params.is_none() { + let mut nonce = vec_try![0u8; 32]?; + self.imp.rng.fill_bytes(&mut nonce); + self.shared_secret_params = Some(SharedSecretParameters { seed: Vec::new(), nonce }); + } + Ok(self.shared_secret_params.as_ref().unwrap().clone()) // safe: filled above + } + + pub(crate) fn compute_shared_secret( + &mut self, + params: &[SharedSecretParameters], + ) -> Result, Error> { + info!("Setting HMAC key from {} shared secret parameters", params.len()); + let local_params = match &self.shared_secret_params { + Some(params) => params, + None => return Err(km_err!(HardwareNotYetAvailable, "no local shared secret params")), + }; + + let context = shared_secret_context(params, local_params)?; + let key = hmac::Key(self.imp.ckdf.ckdf( + &self.dev.keys.kak()?, + kmr_wire::sharedsecret::KEY_AGREEMENT_LABEL.as_bytes(), + &[&context], + kmr_common::crypto::SHA256_DIGEST_LEN, + )?); + + // Potentially hand the negotiated HMAC key off to hardware. + self.device_hmac = Some(self.dev.keys.hmac_key_agreed(&key).unwrap_or_else(|| { + // Key not installed into hardware, so build & use a local impl. + Box::new(SoftDeviceHmac { key }) + })); + self.device_hmac(kmr_wire::sharedsecret::KEY_CHECK_LABEL.as_bytes()) + } +} + +/// Build the shared secret context from the given `params`, which +/// is required to include `must_include` (our own parameters). +pub fn shared_secret_context( + params: &[SharedSecretParameters], + must_include: &SharedSecretParameters, +) -> Result, crate::Error> { + let mut result = Vec::new(); + let mut seen = false; + for param in params { + result.try_extend_from_slice(¶m.seed)?; + if param.nonce.len() != 32 { + return Err(km_err!(InvalidArgument, "nonce len {} not 32", param.nonce.len())); + } + result.try_extend_from_slice(¶m.nonce)?; + if param == must_include { + seen = true; + } + } + if !seen { + Err(km_err!(InvalidArgument, "shared secret params missing local value")) + } else { + Ok(result) + } +} + +/// Device HMAC implementation that holds the HMAC key in memory. +struct SoftDeviceHmac { + key: crypto::hmac::Key, +} + +impl DeviceHmac for SoftDeviceHmac { + fn hmac(&self, imp: &dyn crypto::Hmac, data: &[u8]) -> Result, Error> { + let mut hmac_op = imp.begin(self.key.clone().into(), Digest::Sha256)?; + hmac_op.update(data)?; + hmac_op.finish() + } + + fn get_hmac_key(&self) -> Option { + Some(self.key.clone()) + } +} diff --git a/libs/rust/ta/src/tests.rs b/libs/rust/ta/src/tests.rs new file mode 100644 index 0000000..074061a --- /dev/null +++ b/libs/rust/ta/src/tests.rs @@ -0,0 +1,344 @@ +// Copyright 2022, 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. + +//! Tests + +use crate::{error_rsp, invalid_cbor_rsp_data, keys::SecureKeyWrapper, split_rsp}; +use der::{Decode, Encode}; +use kmr_common::Error; +use kmr_wire::{ + keymint::{ + ErrorCode, KeyFormat, KeyParam, KeyPurpose, NEXT_MESSAGE_SIGNAL_FALSE, + NEXT_MESSAGE_SIGNAL_TRUE, + }, + AsCborValue, +}; + +#[test] +fn test_invalid_data() { + // Cross-check that the hand-encoded invalid CBOR data matches an auto-encoded equivalent. + let rsp = error_rsp(ErrorCode::UnknownError as i32); + let rsp_data = rsp.into_vec().unwrap(); + assert_eq!(rsp_data, invalid_cbor_rsp_data()); +} + +#[test] +fn test_secure_key_wrapper() { + let encoded_str = concat!( + "30820179", // SEQUENCE length 0x179 (SecureKeyWrapper) { + "020100", // INTEGER length 1 value 0x00 (version) + "04820100", // OCTET STRING length 0x100 (encryptedTransportKey) + "aad93ed5924f283b4bb5526fbe7a1412", + "f9d9749ec30db9062b29e574a8546f33", + "c88732452f5b8e6a391ee76c39ed1712", + "c61d8df6213dec1cffbc17a8c6d04c7b", + "30893d8daa9b2015213e219468215532", + "07f8f9931c4caba23ed3bee28b36947e", + "47f10e0a5c3dc51c988a628daad3e5e1", + "f4005e79c2d5a96c284b4b8d7e4948f3", + "31e5b85dd5a236f85579f3ea1d1b8484", + "87470bdb0ab4f81a12bee42c99fe0df4", + "bee3759453e69ad1d68a809ce06b949f", + "7694a990429b2fe81e066ff43e56a216", + "02db70757922a4bcc23ab89f1e35da77", + "586775f423e519c2ea394caf48a28d0c", + "8020f1dcf6b3a68ec246f615ae96dae9", + "a079b1f6eb959033c1af5c125fd94168", + "040c", // OCTET STRING length 0x0c (initializationVector) + "6d9721d08589581ab49204a3", + "302e", // SEQUENCE length 0x2e (KeyDescription) { + "020103", // INTEGER length 1 value 0x03 (keyFormat = RAW) + "3029", // SEQUENCE length 0x29 (AuthorizationList) { + "a108", // [1] context-specific constructed tag=1 length 0x08 { (purpose) + "3106", // SET length 0x06 + "020100", // INTEGER length 1 value 0x00 (Encrypt) + "020101", // INTEGER length 1 value 0x01 (Decrypt) + // } end SET + // } end [1] + "a203", // [2] context-specific constructed tag=2 length 0x02 { (algorithm) + "020120", // INTEGER length 1 value 0x20 (AES) + // } end [2] + "a304", // [3] context-specific constructed tag=3 length 0x04 { (keySize) + "02020100", // INTEGER length 2 value 0x100 + // } end [3] + "a405", // [4] context-specific constructed tag=4 length 0x05 { (blockMode + "3103", // SET length 0x03 { + "020101", // INTEGER length 1 value 0x01 (ECB) + // } end SET + // } end [4] + "a605", // [6] context-specific constructed tag=6 length 0x05 { (padding) + "3103", // SET length 0x03 { + "020140", // INTEGER length 1 value 0x40 (PKCS7) + // } end SET + // } end [5] + "bf837702", // [503] context-specific constructed tag=503=0x1F7 length 0x02 { + // (noAuthRequired) + "0500", // NULL + // } end [503] + // } end SEQUENCE (AuthorizationList) + // } end SEQUENCE (KeyDescription) + "0420", // OCTET STRING length 0x20 (encryptedKey) + "a61c6e247e25b3e6e69aa78eb03c2d4a", + "c20d1f99a9a024a76f35c8e2cab9b68d", + "0410", // OCTET STRING length 0x10 (tag) + "2560c70109ae67c030f00b98b512a670", + // } SEQUENCE (SecureKeyWrapper) + ); + let encoded_bytes = hex::decode(encoded_str).unwrap(); + let secure_key_wrapper = SecureKeyWrapper::from_der(&encoded_bytes).unwrap(); + assert_eq!(secure_key_wrapper.version, 0); + let key_format: KeyFormat = secure_key_wrapper.key_description.key_format.try_into().unwrap(); + assert_eq!(KeyFormat::Raw, key_format); + let authz = secure_key_wrapper.key_description.key_params.auths; + let purpose_values: Vec = authz + .iter() + .filter_map(|param| if let KeyParam::Purpose(v) = param { Some(*v) } else { None }) + .collect(); + assert_eq!(purpose_values.len(), 2); + assert!(purpose_values.contains(&KeyPurpose::Encrypt)); + assert!(purpose_values.contains(&KeyPurpose::Decrypt)); +} + +#[test] +fn test_key_description_encode_decode() { + let encoded_secure_key_wrapper = concat!( + "30820179", // SEQUENCE length 0x179 (SecureKeyWrapper) { + "020100", // INTEGER length 1 value 0x00 (version) + "04820100", // OCTET STRING length 0x100 (encryptedTransportKey) + "aad93ed5924f283b4bb5526fbe7a1412", + "f9d9749ec30db9062b29e574a8546f33", + "c88732452f5b8e6a391ee76c39ed1712", + "c61d8df6213dec1cffbc17a8c6d04c7b", + "30893d8daa9b2015213e219468215532", + "07f8f9931c4caba23ed3bee28b36947e", + "47f10e0a5c3dc51c988a628daad3e5e1", + "f4005e79c2d5a96c284b4b8d7e4948f3", + "31e5b85dd5a236f85579f3ea1d1b8484", + "87470bdb0ab4f81a12bee42c99fe0df4", + "bee3759453e69ad1d68a809ce06b949f", + "7694a990429b2fe81e066ff43e56a216", + "02db70757922a4bcc23ab89f1e35da77", + "586775f423e519c2ea394caf48a28d0c", + "8020f1dcf6b3a68ec246f615ae96dae9", + "a079b1f6eb959033c1af5c125fd94168", + "040c", // OCTET STRING length 0x0c (initializationVector) + "6d9721d08589581ab49204a3", + "302e", // SEQUENCE length 0x2e (KeyDescription) { + "020103", // INTEGER length 1 value 0x03 (keyFormat = RAW) + "3029", // SEQUENCE length 0x29 (AuthorizationList) { + "a108", // [1] context-specific constructed tag=1 length 0x08 { (purpose) + "3106", // SET length 0x06 + "020100", // INTEGER length 1 value 0x00 (Encrypt) + "020101", // INTEGER length 1 value 0x01 (Decrypt) + // } end SET + // } end [1] + "a203", // [2] context-specific constructed tag=2 length 0x02 { (algorithm) + "020120", // INTEGER length 1 value 0x20 (AES) + // } end [2] + "a304", // [3] context-specific constructed tag=3 length 0x04 { (keySize) + "02020100", // INTEGER length 2 value 0x100 + // } end [3] + "a405", // [4] context-specific constructed tag=4 length 0x05 { (blockMode + "3103", // SET length 0x03 { + "020101", // INTEGER length 1 value 0x01 (ECB) + // } end SET + // } end [4] + "a605", // [6] context-specific constructed tag=6 length 0x05 { (padding) + "3103", // SET length 0x03 { + "020140", // INTEGER length 1 value 0x40 (PKCS7) + // } end SET + // } end [5] + "bf837702", // [503] context-specific constructed tag=503=0x1F7 length 0x02 { + // (noAuthRequired) + "0500", // NULL + // } end [503] + // } end SEQUENCE (AuthorizationList) + // } end SEQUENCE (KeyDescription) + "0420", // OCTET STRING length 0x20 (encryptedKey) + "a61c6e247e25b3e6e69aa78eb03c2d4a", + "c20d1f99a9a024a76f35c8e2cab9b68d", + "0410", // OCTET STRING length 0x10 (tag) + "2560c70109ae67c030f00b98b512a670", + // } SEQUENCE (SecureKeyWrapper) + ); + let encoded_key_description_want = concat!( + "302e", // SEQUENCE length 0x2e (KeyDescription) { + "020103", // INTEGER length 1 value 0x03 (keyFormat = RAW) + "3029", // SEQUENCE length 0x29 (AuthorizationList) { + "a108", // [1] context-specific constructed tag=1 length 0x08 { (purpose) + "3106", // SET length 0x06 + "020100", // INTEGER length 1 value 0x00 (Encrypt) + "020101", // INTEGER length 1 value 0x01 (Decrypt) + // } end SET + // } end [1] + "a203", // [2] context-specific constructed tag=2 length 0x02 { (algorithm) + "020120", // INTEGER length 1 value 0x20 (AES) + // } end [2] + "a304", // [3] context-specific constructed tag=3 length 0x04 { (keySize) + "02020100", // INTEGER length 2 value 0x100 + // } end [3] + "a405", // [4] context-specific constructed tag=4 length 0x05 { (blockMode + "3103", // SET length 0x03 { + "020101", // INTEGER length 1 value 0x01 (ECB) + // } end SET + // } end [4] + "a605", // [6] context-specific constructed tag=6 length 0x05 { (padding) + "3103", // SET length 0x03 { + "020140", // INTEGER length 1 value 0x40 (PKCS7) + // } end SET + // } end [5] + "bf837702", // [503] context-specific constructed tag=503=0x1F7 length 0x02 { + // (noAuthRequired) + "0500", // NULL + // } end [503] + // } end SEQUENCE (AuthorizationList) + // } end SEQUENCE (KeyDescription) + ); + let encoded_bytes = hex::decode(encoded_secure_key_wrapper).unwrap(); + let secure_key_wrapper = SecureKeyWrapper::from_der(&encoded_bytes).unwrap(); + let key_description = secure_key_wrapper.key_description; + let encoded_key_description_got = key_description.to_der().unwrap(); + assert_eq!(hex::encode(encoded_key_description_got), encoded_key_description_want); +} + +#[test] +fn test_split_rsp_invalid_input() { + // Check for invalid inputs + let rsp = vec![]; + let result = split_rsp(&rsp, 5); + assert!(result.is_err()); + assert!(matches!(result, Err(Error::Hal(ErrorCode::InvalidArgument, _)))); + + let rsp = vec![0x82, 0x21, 0x80]; + let result = split_rsp(&rsp, 1); + assert!(matches!(result, Err(Error::Hal(ErrorCode::InvalidArgument, _)))); +} + +#[test] +fn test_split_rsp_smaller_input() { + // Test for rsp_data size < max_size + let rsp = vec![0x82, 0x13, 0x82, 0x80, 0x80]; + let result = split_rsp(&rsp, 20).expect("result should not be error"); + assert_eq!(result.len(), 1); + let inner_msg = result.first().expect("single message is expected").as_slice(); + assert_eq!(inner_msg.len(), 6); + let marker = inner_msg[0]; + assert_eq!(marker, NEXT_MESSAGE_SIGNAL_FALSE); + let msg = &inner_msg[1..]; + assert_eq!(msg, rsp); +} + +#[test] +fn test_split_rsp_allowed_size_input() { + // Test for rsp_data size = allowed message length + let rsp = vec![0x82, 0x13, 0x82, 0x80, 0x80]; + let result = split_rsp(&rsp, 6).expect("result should not be error"); + assert_eq!(result.len(), 1); + let inner_msg = result.first().expect("single message is expected").as_slice(); + assert_eq!(inner_msg.len(), 6); + let marker = inner_msg[0]; + assert_eq!(marker, NEXT_MESSAGE_SIGNAL_FALSE); + let msg = &inner_msg[1..]; + assert_eq!(msg, rsp); +} + +#[test] +fn test_split_rsp_max_size_input() { + // Test for rsp_data size = max_size + let rsp = vec![0x82, 0x13, 0x82, 0x80, 0x80, 0x82]; + let result = split_rsp(&rsp, 6).expect("result should not be error"); + assert_eq!(result.len(), 2); + + let inner_msg1 = result.first().expect("a message is expected at index 0").as_slice(); + assert_eq!(inner_msg1.len(), 6); + let marker1 = inner_msg1[0]; + assert_eq!(marker1, NEXT_MESSAGE_SIGNAL_TRUE); + assert_eq!(&inner_msg1[1..], &rsp[..5]); + + let inner_msg2 = result.get(1).expect("a message is expected at index 1").as_slice(); + assert_eq!(inner_msg2.len(), 2); + let marker2 = inner_msg2[0]; + assert_eq!(marker2, NEXT_MESSAGE_SIGNAL_FALSE); + assert_eq!(&inner_msg2[1..], &rsp[5..]); +} + +#[test] +fn test_split_rsp_larger_input_perfect_split() { + // Test for rsp_data size > max_size and it is a perfect split + let rsp1 = vec![0x82, 0x13, 0x82, 0x80, 0x80]; + let rsp2 = vec![0x82, 0x14, 0x82, 0x80, 0x80]; + let rsp3 = vec![0x82, 0x15, 0x82, 0x80, 0x80]; + let mut rsp = vec![]; + rsp.extend_from_slice(&rsp1); + rsp.extend_from_slice(&rsp2); + rsp.extend_from_slice(&rsp3); + let result = split_rsp(&rsp, 6).expect("result should not be error"); + assert_eq!(result.len(), 3); + + let inner_msg1 = result.first().expect("a message is expected at index 0").as_slice(); + assert_eq!(inner_msg1.len(), 6); + let marker1 = inner_msg1[0]; + assert_eq!(marker1, NEXT_MESSAGE_SIGNAL_TRUE); + let msg1 = &inner_msg1[1..]; + assert_eq!(msg1, rsp1); + + let inner_msg2 = result.get(1).expect("a message is expected at index 1").as_slice(); + assert_eq!(inner_msg2.len(), 6); + let marker2 = inner_msg2[0]; + assert_eq!(marker2, NEXT_MESSAGE_SIGNAL_TRUE); + let msg2 = &inner_msg2[1..]; + assert_eq!(msg2, rsp2); + + let inner_msg3 = result.get(2).expect("a message is expected at index 2").as_slice(); + assert_eq!(inner_msg3.len(), 6); + let marker3 = inner_msg3[0]; + assert_eq!(marker3, NEXT_MESSAGE_SIGNAL_FALSE); + let msg3 = &inner_msg3[1..]; + assert_eq!(msg3, rsp3); +} + +#[test] +fn test_split_rsp_larger_input_imperfect_split() { + // Test for rsp_data size > max_size and it is not a perfect split + let rsp1 = vec![0x82, 0x00, 0x81, 0x82, 0x13]; + let rsp2 = vec![0x81, 0x83, 0x41, 0x01, 0x80]; + let rsp3 = vec![0x80]; + let mut rsp = vec![]; + rsp.extend_from_slice(&rsp1); + rsp.extend_from_slice(&rsp2); + rsp.extend_from_slice(&rsp3); + let result = split_rsp(&rsp, 6).expect("result should not be error"); + assert_eq!(result.len(), 3); + + let inner_msg1 = result.first().expect("a message is expected at index 0").as_slice(); + assert_eq!(inner_msg1.len(), 6); + let marker1 = inner_msg1[0]; + assert_eq!(marker1, NEXT_MESSAGE_SIGNAL_TRUE); + let msg1 = &inner_msg1[1..]; + assert_eq!(msg1, rsp1); + + let inner_msg2 = result.get(1).expect("a message is expected at index 1").as_slice(); + assert_eq!(inner_msg2.len(), 6); + let marker2 = inner_msg2[0]; + assert_eq!(marker2, NEXT_MESSAGE_SIGNAL_TRUE); + let msg2 = &inner_msg2[1..]; + assert_eq!(msg2, rsp2); + + let inner_msg3 = result.get(2).expect("a message is expected at index 2").as_slice(); + assert_eq!(inner_msg3.len(), 2); + let marker3 = inner_msg3[0]; + assert_eq!(marker3, NEXT_MESSAGE_SIGNAL_FALSE); + let msg3 = &inner_msg3[1..]; + assert_eq!(msg3, rsp3); +} diff --git a/libs/rust/tests/Cargo.toml b/libs/rust/tests/Cargo.toml new file mode 100644 index 0000000..7fc39fd --- /dev/null +++ b/libs/rust/tests/Cargo.toml @@ -0,0 +1,23 @@ +# Note that Cargo is not an officially supported build tool (Android's Soong is the official +# tool). This Cargo.toml file is included purely for the convenience of KeyMint developers. + +[package] +name = "kmr-tests" +version = "0.1.0" +authors = ["David Drysdale "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +ciborium = { version = "^0.2.0", default-features = false } +env_logger = "^0.9" +hex = "0.4.3" +kmr-common = "*" +kmr-crypto-boring = "*" +kmr-ta = "*" +kmr-wire = "*" +log = "^0.4" +x509-cert = "0.2.4" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(soong)'] } diff --git a/libs/rust/tests/src/bin/auth-keyblob-parse.rs b/libs/rust/tests/src/bin/auth-keyblob-parse.rs new file mode 100644 index 0000000..dee84a3 --- /dev/null +++ b/libs/rust/tests/src/bin/auth-keyblob-parse.rs @@ -0,0 +1,188 @@ +// Copyright 2022, 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. + +//! Utility program to parse a legacy authenticated keyblob. + +// Explicitly include alloc because macros from `kmr_common` assume it. +extern crate alloc; + +use kmr_common::{ + crypto::*, + get_tag_value, + keyblob::{legacy::KeyBlob, *}, + tag, +}; +use kmr_crypto_boring::{eq::BoringEq, hmac::BoringHmac}; +use kmr_wire::{ + keymint, + keymint::{ + Algorithm, DateTime, EcCurve, ErrorCode, KeyCharacteristics, KeyParam, SecurityLevel, + }, +}; +use std::convert::TryInto; + +fn main() { + let mut hex = false; + let args: Vec = std::env::args().collect(); + for arg in &args[1..] { + if arg == "--hex" { + hex = !hex; + } else { + process(arg, hex); + } + } +} + +const SOFTWARE_ROOT_OF_TRUST: &[u8] = b"SW"; + +/// Remove all instances of some tags from a set of `KeyParameter`s. +pub fn remove_tags(params: &[KeyParam], tags: &[keymint::Tag]) -> Vec { + params.iter().filter(|p| !tags.contains(&p.tag())).cloned().collect() +} + +fn process(filename: &str, hex: bool) { + let _ = env_logger::builder().is_test(true).try_init(); + + let mut data: Vec = std::fs::read(filename).unwrap(); + if hex { + let hexdata = std::str::from_utf8(&data).unwrap().trim(); + data = match hex::decode(hexdata) { + Ok(v) => v, + Err(e) => { + eprintln!( + "{}: Failed to parse hex ({:?}): len={} {}", + filename, + e, + hexdata.len(), + hexdata + ); + return; + } + }; + } + let hidden = tag::hidden(&[], SOFTWARE_ROOT_OF_TRUST).unwrap(); + let hmac = BoringHmac {}; + let keyblob = match KeyBlob::deserialize(&hmac, &data, &hidden, BoringEq) { + Ok(k) => k, + Err(e) => { + eprintln!("{}: Failed to parse: {:?}", filename, e); + return; + } + }; + println!( + "{}: KeyBlob {{\n key_material=...(len {}),\n hw_enforced={:?},\n sw_enforced={:?},\n}}", + filename, + keyblob.key_material.len(), + keyblob.hw_enforced, + keyblob.sw_enforced + ); + + #[cfg(soong)] + { + // Also round-trip the keyblob to binary and expect to get back where we started. + let regenerated_data = keyblob.serialize(&hmac, &hidden).unwrap(); + assert_eq!(®enerated_data[..regenerated_data.len()], &data[..data.len()]); + } + + // Create a PlaintextKeyBlob from the data. + let mut combined = keyblob.hw_enforced.clone(); + combined.extend_from_slice(&keyblob.sw_enforced); + + let algo_val = get_tag_value!(&combined, Algorithm, ErrorCode::InvalidArgument) + .expect("characteristics missing algorithm"); + + let raw_key = keyblob.key_material.clone(); + let key_material = match algo_val { + Algorithm::Aes => KeyMaterial::Aes(aes::Key::new(raw_key).unwrap().into()), + Algorithm::TripleDes => KeyMaterial::TripleDes( + des::Key(raw_key.try_into().expect("Incorrect length for 3DES key")).into(), + ), + Algorithm::Hmac => KeyMaterial::Hmac(hmac::Key(raw_key).into()), + Algorithm::Ec => { + let curve_val = tag::get_ec_curve(&combined).expect("characteristics missing EC curve"); + match curve_val { + EcCurve::P224 => KeyMaterial::Ec( + EcCurve::P224, + CurveType::Nist, + ec::Key::P224(ec::NistKey(raw_key)).into(), + ), + EcCurve::P256 => KeyMaterial::Ec( + EcCurve::P256, + CurveType::Nist, + ec::Key::P256(ec::NistKey(raw_key)).into(), + ), + EcCurve::P384 => KeyMaterial::Ec( + EcCurve::P384, + CurveType::Nist, + ec::Key::P384(ec::NistKey(raw_key)).into(), + ), + EcCurve::P521 => KeyMaterial::Ec( + EcCurve::P521, + CurveType::Nist, + ec::Key::P521(ec::NistKey(raw_key)).into(), + ), + EcCurve::Curve25519 => { + ec::import_pkcs8_key(&raw_key).expect("curve25519 key in PKCS#8 format") + } + } + } + Algorithm::Rsa => KeyMaterial::Rsa(rsa::Key(raw_key).into()), + }; + + // Test the `tag::extract_key_characteristics()` entrypoint by comparing what it + // produces against the keyblob's combined characteristics. To do this, we need + // to simulate a key-generation operation by: + // - removing the KeyMint-added tags + // - removing any Keystore-enforced tags + // - adding any tags required for key generation. + let mut filtered = keyblob.hw_enforced.clone(); + filtered.extend_from_slice(&keyblob.sw_enforced); + let filtered = remove_tags(&filtered, tag::AUTO_ADDED_CHARACTERISTICS); + let mut filtered = remove_tags(&filtered, tag::KEYSTORE_ENFORCED_CHARACTERISTICS); + filtered.sort_by(tag::legacy::param_compare); + + let mut keygen_params = filtered.clone(); + match tag::get_algorithm(&filtered).unwrap() { + Algorithm::Ec | Algorithm::Rsa => { + keygen_params.push(KeyParam::CertificateNotBefore(DateTime { ms_since_epoch: 0 })); + keygen_params.push(KeyParam::CertificateNotAfter(DateTime { + ms_since_epoch: 1_900_000_000_000, + })); + } + _ => {} + } + keygen_params.sort_by(tag::legacy::param_compare); + let (extracted, _) = tag::extract_key_gen_characteristics( + kmr_common::tag::SecureStorage::Unavailable, + &keygen_params, + SecurityLevel::Software, + ) + .unwrap(); + assert_eq!(extracted[0].authorizations, filtered); + + let plaintext_keyblob = PlaintextKeyBlob { + characteristics: vec![ + KeyCharacteristics { + security_level: SecurityLevel::TrustedEnvironment, + authorizations: keyblob.hw_enforced, + }, + KeyCharacteristics { + security_level: SecurityLevel::Software, + authorizations: keyblob.sw_enforced, + }, + ], + key_material, + }; + println!("{}: => {:?}", filename, plaintext_keyblob); +} diff --git a/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs b/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs new file mode 100644 index 0000000..73bfb2f --- /dev/null +++ b/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs @@ -0,0 +1,82 @@ +// Copyright 2022, 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. + +//! Utility program to parse a legacy encrypted keyblob (but not decrypt it). + +use kmr_common::keyblob::legacy::EncryptedKeyBlob; + +fn main() { + let mut hex = false; + let args: Vec = std::env::args().collect(); + for arg in &args[1..] { + if arg == "--hex" { + hex = !hex; + } else { + process(arg, hex); + } + } +} + +fn process(filename: &str, hex: bool) { + let _ = env_logger::builder().is_test(true).try_init(); + + println!("File: {}", filename); + let mut data: Vec = std::fs::read(filename).unwrap(); + if hex { + let hexdata = std::str::from_utf8(&data).unwrap().trim(); + data = match hex::decode(hexdata) { + Ok(v) => v, + Err(e) => { + eprintln!( + "{}: Failed to parse hex ({:?}): len={} {}", + filename, + e, + hexdata.len(), + hexdata + ); + return; + } + }; + } + let keyblob = match EncryptedKeyBlob::deserialize(&data) { + Ok(k) => k, + Err(e) => { + eprintln!("{}: Failed to parse: {:?}", filename, e); + return; + } + }; + println!( + "{}, KeyBlob {{\n format={:?}\n nonce={},\n ciphertext=...(len {}),\n tag={},", + filename, + keyblob.format, + hex::encode(&keyblob.nonce), + keyblob.ciphertext.len(), + hex::encode(&keyblob.tag) + ); + if let Some(kdf_version) = keyblob.kdf_version { + println!(" kdf_version={}", kdf_version); + } + if let Some(addl_info) = keyblob.addl_info { + println!(" addl_info={}", addl_info); + } + println!(" hw_enforced={:?},\n sw_enforced={:?},", keyblob.hw_enforced, keyblob.sw_enforced); + if let Some(key_slot) = keyblob.key_slot { + println!(" key_slot={}", key_slot); + } + println!("}}"); + + // Also round-trip the keyblob to binary. + let regenerated_data = keyblob.serialize().unwrap(); + assert_eq!(regenerated_data, data); +} diff --git a/libs/rust/tests/src/lib.rs b/libs/rust/tests/src/lib.rs new file mode 100644 index 0000000..7c0fff9 --- /dev/null +++ b/libs/rust/tests/src/lib.rs @@ -0,0 +1,632 @@ +// Copyright 2022, 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. + +//! Test methods to confirm basic functionality of trait implementations. + +use core::convert::TryInto; +use kmr_common::crypto::{ + aes, des, hmac, Aes, AesCmac, Ckdf, ConstTimeEq, Des, Hkdf, Hmac, MonotonicClock, Rng, Sha256, + SymmetricOperation, +}; +use kmr_common::{keyblob, keyblob::SlotPurpose}; +use kmr_ta::device::{SigningAlgorithm, SigningKey, SigningKeyType}; +use kmr_wire::{keymint::Digest, rpc}; +use std::collections::HashMap; +use x509_cert::der::{Decode, Encode}; + +/// Test basic [`Rng`] functionality. +pub fn test_rng(rng: &mut R) { + let u1 = rng.next_u64(); + let u2 = rng.next_u64(); + assert_ne!(u1, u2); + + let mut b1 = [0u8; 16]; + let mut b2 = [0u8; 16]; + rng.fill_bytes(&mut b1); + rng.fill_bytes(&mut b2); + assert_ne!(b1, b2); + + rng.add_entropy(&b1); + rng.add_entropy(&[]); + rng.fill_bytes(&mut b1); + assert_ne!(b1, b2); +} + +/// Test basic [`ConstTimeEq`] functionality. Does not test the key constant-time property though. +pub fn test_eq(comparator: E) { + let b0 = []; + let b1 = [0u8, 1u8, 2u8]; + let b2 = [1u8, 1u8, 2u8]; + let b3 = [0u8, 1u8, 3u8]; + let b4 = [0u8, 1u8, 2u8, 3u8]; + let b5 = [42; 4096]; + let mut b6 = [42; 4096]; + b6[4095] = 43; + assert!(comparator.eq(&b0, &b0)); + assert!(comparator.eq(&b5, &b5)); + + assert!(comparator.ne(&b0, &b1)); + assert!(comparator.ne(&b0, &b2)); + assert!(comparator.ne(&b0, &b3)); + assert!(comparator.ne(&b0, &b4)); + assert!(comparator.ne(&b0, &b5)); + assert!(comparator.eq(&b1, &b1)); + assert!(comparator.ne(&b1, &b2)); + assert!(comparator.ne(&b1, &b3)); + assert!(comparator.ne(&b1, &b4)); + assert!(comparator.ne(&b5, &b6)); +} + +/// Test basic [`MonotonicClock`] functionality. +pub fn test_clock(clock: C) { + let t1 = clock.now(); + let t2 = clock.now(); + assert!(t2.0 >= t1.0); + std::thread::sleep(std::time::Duration::from_millis(400)); + let t3 = clock.now(); + assert!(t3.0 > (t1.0 + 200)); +} + +/// Test basic HKDF functionality. +pub fn test_hkdf(hmac: H) { + struct TestCase { + ikm: &'static str, + salt: &'static str, + info: &'static str, + out_len: usize, + want: &'static str, + } + + const HKDF_TESTS: &[TestCase] = &[ + // RFC 5869 section A.1 + TestCase { + ikm: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + salt: "000102030405060708090a0b0c", + info: "f0f1f2f3f4f5f6f7f8f9", + out_len: 42, + want: concat!( + "3cb25f25faacd57a90434f64d0362f2a", + "2d2d0a90cf1a5a4c5db02d56ecc4c5bf", + "34007208d5b887185865", + ), + }, + // RFC 5869 section A.2 + TestCase { + ikm: concat!( + "000102030405060708090a0b0c0d0e0f", + "101112131415161718191a1b1c1d1e1f", + "202122232425262728292a2b2c2d2e2f", + "303132333435363738393a3b3c3d3e3f", + "404142434445464748494a4b4c4d4e4f", + ), + salt: concat!( + "606162636465666768696a6b6c6d6e6f", + "707172737475767778797a7b7c7d7e7f", + "808182838485868788898a8b8c8d8e8f", + "909192939495969798999a9b9c9d9e9f", + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", + ), + info: concat!( + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf", + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf", + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf", + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + ), + out_len: 82, + want: concat!( + "b11e398dc80327a1c8e7f78c596a4934", + "4f012eda2d4efad8a050cc4c19afa97c", + "59045a99cac7827271cb41c65e590e09", + "da3275600c2f09b8367793a9aca3db71", + "cc30c58179ec3e87c14c01d5c1f3434f", + "1d87", + ), + }, + // RFC 5869 section A.3 + TestCase { + ikm: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + salt: "", + info: "", + out_len: 42, + want: concat!( + "8da4e775a563c18f715f802a063c5a31", + "b8a11f5c5ee1879ec3454e5f3c738d2d", + "9d201395faa4b61a96c8", + ), + }, + ]; + + for (i, test) in HKDF_TESTS.iter().enumerate() { + let ikm = hex::decode(test.ikm).unwrap(); + let salt = hex::decode(test.salt).unwrap(); + let info = hex::decode(test.info).unwrap(); + + let got = hmac.hkdf(&salt, &ikm, &info, test.out_len).unwrap(); + assert_eq!(hex::encode(got), test.want, "incorrect HKDF result for case {}", i); + } +} + +/// Test basic [`Hmac`] functionality. +pub fn test_hmac(hmac: H) { + struct TestCase { + digest: Digest, + tag_size: usize, + key: &'static [u8], + data: &'static [u8], + expected_mac: &'static str, + } + + const HMAC_TESTS : &[TestCase] = &[ + TestCase { + digest: Digest::Sha256, + tag_size: 32, + data: b"Hello", + key: b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + expected_mac: "e0ff02553d9a619661026c7aa1ddf59b7b44eac06a9908ff9e19961d481935d4", + }, + TestCase { + digest: Digest::Sha512, + tag_size: 64, + data: b"Hello", + key: b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + expected_mac: "481e10d823ba64c15b94537a3de3f253c16642451ac45124dd4dde120bf1e5c15e55487d55ba72b43039f235226e7954cd5854b30abc4b5b53171a4177047c9b", + }, + // empty data + TestCase { + digest: Digest::Sha256, + tag_size: 32, + data: &[], + key: b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + expected_mac: "07eff8b326b7798c9ccfcbdbe579489ac785a7995a04618b1a2813c26744777d", + }, + + // Test cases from RFC 4231 Section 4.2 + TestCase { + digest: Digest::Sha224, + tag_size: 224/8, + key: &[0x0b; 20], + data: b"Hi There", + expected_mac: concat!( + "896fb1128abbdf196832107cd49df33f", + "47b4b1169912ba4f53684b22", + ), + }, + TestCase { + digest: Digest::Sha256, + tag_size: 256/8, + key: &[0x0b; 20], + data: b"Hi There", + expected_mac: concat!( + "b0344c61d8db38535ca8afceaf0bf12b", + "881dc200c9833da726e9376c2e32cff7", + ), + }, + TestCase { + digest: Digest::Sha384, + tag_size: 384/8, + key: &[0x0b; 20], + data: b"Hi There", + expected_mac: concat!( + "afd03944d84895626b0825f4ab46907f", + "15f9dadbe4101ec682aa034c7cebc59c", + "faea9ea9076ede7f4af152e8b2fa9cb6", + ), + }, + TestCase { + digest: Digest::Sha512, + tag_size: 512/8, + key: &[0x0b; 20], + data: b"Hi There", + expected_mac: concat!( + "87aa7cdea5ef619d4ff0b4241a1d6cb0", + "2379f4e2ce4ec2787ad0b30545e17cde", + "daa833b7d6b8a702038b274eaea3f4e4", + "be9d914eeb61f1702e696c203a126854" + ), + }, + // Test cases from RFC 4231 Section 4.3 + TestCase { + digest: Digest::Sha224, + tag_size: 224/8, + key: b"Jefe", + data: b"what do ya want for nothing?", + expected_mac: concat!( + "a30e01098bc6dbbf45690f3a7e9e6d0f", + "8bbea2a39e6148008fd05e44" + ), + }, + TestCase { + digest: Digest::Sha256, + tag_size: 256/8, + key: b"Jefe", + data: b"what do ya want for nothing?", + expected_mac: concat!( + "5bdcc146bf60754e6a042426089575c7", + "5a003f089d2739839dec58b964ec3843" + ), + }, + TestCase { + digest: Digest::Sha384, + tag_size: 384/8, + key: b"Jefe", + data: b"what do ya want for nothing?", + expected_mac: concat!( + "af45d2e376484031617f78d2b58a6b1b", + "9c7ef464f5a01b47e42ec3736322445e", + "8e2240ca5e69e2c78b3239ecfab21649" + ), + }, + TestCase { + digest: Digest::Sha512, + tag_size: 512/8, + key: b"Jefe", + data: b"what do ya want for nothing?", + expected_mac: concat!( + "164b7a7bfcf819e2e395fbe73b56e0a3", + "87bd64222e831fd610270cd7ea250554", + "9758bf75c05a994a6d034f65f8f0e6fd", + "caeab1a34d4a6b4b636e070a38bce737" + ), + }, + // Test cases from RFC 4231 Section 4.4 + TestCase { + digest: Digest::Sha224, + tag_size: 224/8, + key: &[0xaa; 20], + data: &[0xdd; 50], + expected_mac: concat!( + "7fb3cb3588c6c1f6ffa9694d7d6ad264", + "9365b0c1f65d69d1ec8333ea" + ), + }, + TestCase { + digest: Digest::Sha256, + tag_size: 256/8, + key: &[0xaa; 20], + data: &[0xdd; 50], + expected_mac: concat!( + "773ea91e36800e46854db8ebd09181a7", + "2959098b3ef8c122d9635514ced565fe" + ), + }, + TestCase { + digest: Digest::Sha384, + tag_size: 384/8, + key: &[0xaa; 20], + data: &[0xdd; 50], + expected_mac: concat!( + "88062608d3e6ad8a0aa2ace014c8a86f", + "0aa635d947ac9febe83ef4e55966144b", + "2a5ab39dc13814b94e3ab6e101a34f27" + ), + }, + TestCase { + digest: Digest::Sha512, + tag_size: 512/8, + key: &[0xaa; 20], + data: &[0xdd; 50], + expected_mac: concat!( + "fa73b0089d56a284efb0f0756c890be9", + "b1b5dbdd8ee81a3655f83e33b2279d39", + "bf3e848279a722c806b485a47e67c807", + "b946a337bee8942674278859e13292fb" + ), + }, + ]; + + for (i, test) in HMAC_TESTS.iter().enumerate() { + let mut op = hmac.begin(hmac::Key(test.key.to_vec()).into(), test.digest).unwrap(); + op.update(test.data).unwrap(); + let mut mac = op.finish().unwrap(); + mac.truncate(test.tag_size); + + assert_eq!( + hex::encode(&mac), + test.expected_mac[..(test.tag_size * 2)], + "incorrect mac in test case {}", + i + ); + } +} + +/// Test basic [`AesCmac`] functionality. +pub fn test_aes_cmac(cmac: M) { + // Test vectors from RFC 4493. + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").expect("Could not decode key"); + let key = aes::Key::new(key).unwrap(); + let data = hex::decode("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710").expect("Could not decode data"); + let expected = vec![ + (0usize, "bb1d6929e95937287fa37d129b756746"), + (16usize, "070a16b46b4d4144f79bdd9dd04a287c"), + (40usize, "dfa66747de9ae63030ca32611497c827"), + (64usize, "51f0bebf7e3b9d92fc49741779363cfe"), + ] + .into_iter() + .collect::>(); + + for (len, want) in expected { + let mut op = cmac.begin(key.clone().into()).unwrap(); + op.update(&data[..len]).unwrap(); + let cmac = op.finish().unwrap(); + + assert_eq!(hex::encode(&cmac[..16]), want); + } +} + +/// Test `ckdf()` functionality based on an underlying [`AesCmac`] implementation. +pub fn test_ckdf(kdf: T) { + // Test data manually generated from Android C++ implementation. + let key = aes::Key::new(vec![0; 32]).unwrap(); + let label = b"KeymasterSharedMac"; + let v0 = vec![0x00, 0x00, 0x00, 0x00]; + let v1 = vec![0x01, 0x01, 0x01, 0x01]; + let v2 = vec![0x02, 0x02, 0x02, 0x02]; + let v3 = vec![0x03, 0x03, 0x03, 0x03]; + + let result = kdf.ckdf(&key.into(), label, &[&v0, &v1, &v2, &v3], 32).unwrap(); + assert_eq!( + hex::encode(result), + concat!("ac9af88a02241f53d43056a4676c42ee", "f06825755e419e7bd20f4e57487717aa") + ); +} + +/// Test AES-GCM functionality. +pub fn test_aes_gcm(aes: A) { + struct TestCase { + key: &'static str, + iv: &'static str, + aad: &'static str, + msg: &'static str, + ct: &'static str, + tag: &'static str, + } + // Test vectors from https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + let tests = vec![ + TestCase { + key: "5b9604fe14eadba931b0ccf34843dab9", + iv: "028318abc1824029138141a2", + aad: "", + msg: "001d0c231287c1182784554ca3a21908", + ct: "26073cc1d851beff176384dc9896d5ff", + tag: "0a3ea7a5487cb5f7d70fb6c58d038554", + }, + TestCase { + key: "5b9604fe14eadba931b0ccf34843dab9", + iv: "921d2507fa8007b7bd067d34", + aad: "00112233445566778899aabbccddeeff", + msg: "001d0c231287c1182784554ca3a21908", + ct: "49d8b9783e911913d87094d1f63cc765", + tag: "1e348ba07cca2cf04c618cb4d43a5b92", + }, + ]; + for test in tests { + let key = hex::decode(test.key).unwrap(); + let iv = hex::decode(test.iv).unwrap(); + assert_eq!(iv.len(), 12); // Only 96-bit nonces supported. + let aad = hex::decode(test.aad).unwrap(); + let msg = hex::decode(test.msg).unwrap(); + let tag = hex::decode(test.tag).unwrap(); + assert_eq!(tag.len(), 16); // Test data includes full 128-bit tag + + let aes_key = aes::Key::new(key.clone()).unwrap(); + let mut op = aes + .begin_aead( + aes_key.into(), + aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() }, + SymmetricOperation::Encrypt, + ) + .unwrap(); + op.update_aad(&aad).unwrap(); + let mut got_ct = op.update(&msg).unwrap(); + got_ct.extend_from_slice(&op.finish().unwrap()); + assert_eq!(format!("{}{}", test.ct, test.tag), hex::encode(&got_ct)); + + let aes_key = aes::Key::new(key.clone()).unwrap(); + let mut op = aes + .begin_aead( + aes_key.into(), + aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() }, + SymmetricOperation::Decrypt, + ) + .unwrap(); + op.update_aad(&aad).unwrap(); + let mut got_pt = op.update(&got_ct).unwrap(); + got_pt.extend_from_slice(&op.finish().unwrap()); + assert_eq!(test.msg, hex::encode(&got_pt)); + + // Truncated tag should still decrypt. + let aes_key = aes::Key::new(key.clone()).unwrap(); + let mut op = match aes.begin_aead( + aes_key.into(), + aes::GcmMode::GcmTag12 { nonce: iv.clone().try_into().unwrap() }, + SymmetricOperation::Decrypt, + ) { + Ok(c) => c, + Err(_) => return, + }; + op.update_aad(&aad).unwrap(); + let mut got_pt = op.update(&got_ct[..got_ct.len() - 4]).unwrap(); + got_pt.extend_from_slice(&op.finish().unwrap()); + assert_eq!(test.msg, hex::encode(&got_pt)); + + // Corrupted ciphertext should not decrypt. + let aes_key = aes::Key::new(key).unwrap(); + let mut op = match aes.begin_aead( + aes_key.into(), + aes::GcmMode::GcmTag12 { nonce: iv.try_into().unwrap() }, + SymmetricOperation::Decrypt, + ) { + Ok(c) => c, + Err(_) => return, + }; + op.update_aad(&aad).unwrap(); + let mut corrupt_ct = got_ct.clone(); + corrupt_ct[0] ^= 0x01; + let _corrupt_pt = op.update(&corrupt_ct).unwrap(); + let result = op.finish(); + assert!(result.is_err()); + } +} + +/// Test basic triple-DES functionality. +pub fn test_des(des: D) { + struct TestCase { + key: &'static str, + msg: &'static str, + ct: &'static str, + } + let tests = vec![ + TestCase { + key: "800000000000000000000000000000000000000000000000", + msg: "0000000000000000", + ct: "95a8d72813daa94d", + }, + TestCase { + key: "000000000000000000000000000000002000000000000000", + msg: "0000000000000000", + ct: "7ad16ffb79c45926", + }, + ]; + for test in tests { + let key = hex::decode(test.key).unwrap(); + let msg = hex::decode(test.msg).unwrap(); + + let des_key = des::Key::new(key.clone()).unwrap(); + let mut op = des + .begin(des_key.clone().into(), des::Mode::EcbNoPadding, SymmetricOperation::Encrypt) + .unwrap(); + let mut got_ct = op.update(&msg).unwrap(); + got_ct.extend_from_slice(&op.finish().unwrap()); + assert_eq!(test.ct, hex::encode(&got_ct)); + + let mut op = des + .begin(des_key.into(), des::Mode::EcbNoPadding, SymmetricOperation::Decrypt) + .unwrap(); + let mut got_pt = op.update(&got_ct).unwrap(); + got_pt.extend_from_slice(&op.finish().unwrap()); + assert_eq!(test.msg, hex::encode(&got_pt)); + } +} + +/// Test basic SHA-256 functionality. +pub fn test_sha256(sha256: S) { + struct TestCase { + msg: &'static [u8], + want: &'static str, + } + let tests = vec![ + TestCase { + msg: b"", + want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + TestCase { + msg: b"abc", + want: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + }, + ]; + for test in tests { + let got = sha256.hash(test.msg).unwrap(); + assert_eq!(hex::encode(got), test.want, "for input {}", hex::encode(test.msg)); + } +} + +/// Test secure deletion secret management. +/// +/// Warning: this test will use slots in the provided manager, and may leak slots on failure. +pub fn test_sdd_mgr(mut sdd_mgr: M, mut rng: R) { + let (slot1, sdd1) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + assert!(sdd_mgr.get_secret(slot1).unwrap() == sdd1); + assert!(sdd_mgr.get_secret(slot1).unwrap() == sdd1); + + // A second instance should share factory reset secret but not per-key secret. + let (slot2, sdd2) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + assert!(sdd_mgr.get_secret(slot2).unwrap() == sdd2); + assert_eq!(sdd1.factory_reset_secret, sdd2.factory_reset_secret); + assert_ne!(sdd1.secure_deletion_secret, sdd2.secure_deletion_secret); + + assert!(sdd_mgr.delete_secret(slot1).is_ok()); + assert!(sdd_mgr.get_secret(slot1).is_err()); + assert!(sdd_mgr.delete_secret(slot1).is_err()); + + assert!(sdd_mgr.delete_secret(slot2).is_ok()); +} + +/// Test that attestation certificates parse as X.509 structures. +pub fn test_signing_cert_parse( + certs: T, + is_strongbox: bool, +) { + let avail = if is_strongbox { + vec![SigningKey::Batch, SigningKey::DeviceUnique] + } else { + vec![SigningKey::Batch] + }; + for which in avail { + for algo_hint in [SigningAlgorithm::Ec, SigningAlgorithm::Rsa] { + let info = SigningKeyType { which, algo_hint }; + let chain = certs + .cert_chain(info) + .unwrap_or_else(|_| panic!("failed to retrieve chain for {:?}", info)); + + // Check that the attestation chain looks basically valid (parses as DER, + // has subject/issuer match). + let mut prev_subject_data = vec![]; + for (idx, cert) in chain.iter().rev().enumerate() { + let cert = x509_cert::Certificate::from_der(&cert.encoded_certificate) + .expect("failed to parse cert"); + + let subject_data = cert.tbs_certificate.subject.to_der().unwrap(); + let issuer_data = cert.tbs_certificate.issuer.to_der().unwrap(); + if idx == 0 { + // First cert should be self-signed, and so have subject==issuer. + assert_eq!( + hex::encode(&subject_data), + hex::encode(&issuer_data), + "root cert has subject != issuer for {:?}", + info + ); + } else { + // Issuer of cert should be the subject of the previous cert. + assert_eq!( + hex::encode(&prev_subject_data), + hex::encode(&issuer_data), + "cert {} has issuer != prev_cert.subject for {:?}", + idx, + info + ) + } + prev_subject_data.clone_from(&subject_data); + } + } + } +} + +/// Simple smoke test for an `RetrieveRpcArtifacts` trait implementation. +pub fn test_retrieve_rpc_artifacts( + rpc: T, + hmac: &dyn Hmac, + hkdf: &dyn Hkdf, +) { + assert!(rpc.get_dice_info(rpc::TestMode(false)).is_ok()); + + let context = b"abcdef"; + let data1 = rpc.derive_bytes_from_hbk(hkdf, context, 16).expect("failed to derive from HBK"); + let data2 = rpc.derive_bytes_from_hbk(hkdf, context, 16).expect("failed to derive from HBK"); + assert_eq!(data1, data2, "derive_bytes_from_hbk() method should be deterministic"); + + let data1 = rpc.compute_hmac_sha256(hmac, hkdf, context).expect("failed to perform HMAC"); + let data2 = rpc.compute_hmac_sha256(hmac, hkdf, context).expect("failed to perform HMAC"); + assert_eq!(data1, data2, "compute_hmac_sha256() method should be deterministic"); +} diff --git a/libs/rust/tests/tests/keyblob_test.rs b/libs/rust/tests/tests/keyblob_test.rs new file mode 100644 index 0000000..431312b --- /dev/null +++ b/libs/rust/tests/tests/keyblob_test.rs @@ -0,0 +1,202 @@ +// Copyright 2022, 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. + +// Integration test. +#![cfg(soong)] + +// Explicitly include alloc because macros from `kmr_common` assume it. +extern crate alloc; + +use kmr_common::{crypto, crypto::Rng, expect_err, keyblob, keyblob::legacy::KeyBlob}; +use kmr_crypto_boring::aes::BoringAes; +use kmr_crypto_boring::eq::BoringEq; +use kmr_crypto_boring::hmac::BoringHmac; +use kmr_crypto_boring::rng::BoringRng; +use kmr_wire::{keymint, keymint::KeyParam}; + +#[test] +fn test_encrypted_keyblob_roundtrip() { + let aes = BoringAes; + let hmac = BoringHmac; + let mut rng = BoringRng; + let mut root_key = crypto::hmac::Key(vec![0u8; 32]); + rng.fill_bytes(&mut root_key.0); + let root_key = crypto::OpaqueOr::Explicit(root_key); + let plaintext_keyblob = keyblob::PlaintextKeyBlob { + characteristics: vec![keymint::KeyCharacteristics { + security_level: keymint::SecurityLevel::TrustedEnvironment, + authorizations: vec![ + KeyParam::Algorithm(keymint::Algorithm::Aes), + KeyParam::BlockMode(keymint::BlockMode::Ecb), + KeyParam::Padding(keymint::PaddingMode::None), + ], + }], + key_material: crypto::KeyMaterial::Aes(crypto::aes::Key::Aes128([0u8; 16]).into()), + }; + let hidden = vec![ + KeyParam::ApplicationId(b"app_id".to_vec()), + KeyParam::ApplicationData(b"app_data".to_vec()), + ]; + + let encrypted_keyblob = keyblob::encrypt( + keymint::SecurityLevel::TrustedEnvironment, + None, + &aes, + &hmac, + &mut rng, + &root_key, + &[], + plaintext_keyblob.clone(), + hidden.clone(), + keyblob::SlotPurpose::KeyGeneration, + ) + .unwrap(); + + let recovered_keyblob = + keyblob::decrypt(None, &aes, &hmac, &root_key, encrypted_keyblob, hidden).unwrap(); + assert_eq!(plaintext_keyblob, recovered_keyblob); +} + +#[test] +fn test_serialize_authenticated_legacy_keyblob() { + let hidden = kmr_common::keyblob::legacy::hidden(&[], &[b"SW"]).unwrap(); + let tests = vec![( + concat!( + "00", // version + "02000000", + "bbbb", // key material + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + "0000000000000000", // hmac + ), + KeyBlob { key_material: vec![0xbb, 0xbb], hw_enforced: vec![], sw_enforced: vec![] }, + )]; + for (hex_data, want) in tests { + let mut data = hex::decode(hex_data).unwrap(); + + // Key blob cannot be deserialized without a correct MAC. + let hmac = BoringHmac {}; + let result = KeyBlob::deserialize(&hmac, &data, &hidden, BoringEq); + expect_err!(result, "invalid key blob"); + + fix_hmac(&mut data, &hidden); + let got = KeyBlob::deserialize(&hmac, &data, &hidden, BoringEq).unwrap(); + assert_eq!(got, want); + let new_data = got.serialize(&hmac, &hidden).unwrap(); + assert_eq!(new_data, data); + } +} + +#[test] +fn test_deserialize_authenticated_legacy_keyblob_fail() { + let hidden = kmr_common::keyblob::legacy::hidden(&[], &[b"SW"]).unwrap(); + let tests = vec![ + ( + concat!( + "02", // version + "02000000", + "bbbb", // key material + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + "0000000000000000", // hmac + ), + "unexpected blob version 2", + ), + ( + concat!( + "00", // version + "02000000", + "bbbb", // key material + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + "00", // bonus byte + "0000000000000000", // hmac + ), + "extra data (len 1)", + ), + ]; + let hmac = BoringHmac {}; + for (hex_data, msg) in tests { + let mut data = hex::decode(hex_data).unwrap(); + fix_hmac(&mut data, &hidden); + let result = KeyBlob::deserialize(&hmac, &data, &hidden, BoringEq); + expect_err!(result, msg); + } +} + +#[test] +fn test_deserialize_authenticated_legacy_keyblob_truncated() { + let hidden = kmr_common::keyblob::legacy::hidden(&[], &[b"SW"]).unwrap(); + let mut data = hex::decode(concat!( + "00", // version + "02000000", + "bbbb", // key material + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + concat!( + "00000000", // no blob data + "00000000", // no params + "00000000", // zero size of params + ), + "0000000000000000", // hmac + )) + .unwrap(); + fix_hmac(&mut data, &hidden); + let hmac = BoringHmac {}; + assert!(KeyBlob::deserialize(&hmac, &data, &hidden, BoringEq).is_ok()); + + for len in 0..data.len() - 1 { + // Any truncation of this data is invalid. + assert!( + KeyBlob::deserialize(&hmac, &data[..len], &hidden, BoringEq).is_err(), + "deserialize of data[..{}] subset (len={}) unexpectedly succeeded", + len, + data.len() + ); + } +} + +fn fix_hmac(data: &mut [u8], hidden: &[KeyParam]) { + let hmac = BoringHmac {}; + let mac_offset = data.len() - KeyBlob::MAC_LEN; + let mac = KeyBlob::compute_hmac(&hmac, &data[..mac_offset], hidden).unwrap(); + data[mac_offset..].copy_from_slice(&mac); +} diff --git a/libs/rust/wire/Cargo.toml b/libs/rust/wire/Cargo.toml new file mode 100644 index 0000000..ce3d1e7 --- /dev/null +++ b/libs/rust/wire/Cargo.toml @@ -0,0 +1,30 @@ +# Note that Cargo is not an officially supported build tool (Android's Soong is the official +# tool). This Cargo.toml file is included purely for the convenience of KeyMint developers. + +[package] +name = "kmr-wire" +version = "0.1.0" +authors = ["David Drysdale "] +edition = "2021" +license = "Apache-2.0" + +[features] +default = ["hal_v2", "hal_v3", "hal_v4"] +# Include support for types added in v4 of the KeyMint HAL. +hal_v4 = ["hal_v3", "hal_v2"] +# Include support for types added in v3 of the KeyMint HAL. +hal_v3 = ["hal_v2"] +# Include support for types added in v2 of the KeyMint HAL. +hal_v2 = [] + +[dependencies] +ciborium = { version = "^0.2.2", default-features = false } +ciborium-io = "^0.2.0" +coset = "0.3.3" +enumn = "0.1.4" +kmr-derive = "*" +log = "^0.4" +zeroize = { version = "^1.5.6", features = ["alloc", "zeroize_derive"] } + +[dev-dependencies] +hex = "0.4.3" diff --git a/libs/rust/wire/fuzz/.gitignore b/libs/rust/wire/fuzz/.gitignore new file mode 100644 index 0000000..a092511 --- /dev/null +++ b/libs/rust/wire/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/libs/rust/wire/fuzz/Cargo.toml b/libs/rust/wire/fuzz/Cargo.toml new file mode 100644 index 0000000..a2e0ab3 --- /dev/null +++ b/libs/rust/wire/fuzz/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "kmr-wire-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.kmr-wire] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "message" +path = "fuzz_targets/message.rs" +test = false +doc = false + +[[bin]] +name = "legacy_message" +path = "fuzz_targets/legacy_message.rs" +test = false +doc = false + +[patch.crates-io] +kmr-derive = { path = "../../derive" } diff --git a/libs/rust/wire/fuzz/fuzz_targets/legacy_message.rs b/libs/rust/wire/fuzz/fuzz_targets/legacy_message.rs new file mode 100644 index 0000000..cf1d7c9 --- /dev/null +++ b/libs/rust/wire/fuzz/fuzz_targets/legacy_message.rs @@ -0,0 +1,23 @@ +// Copyright 2024, 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. + +//! Fuzzer for legacy request message parsing. + +#![no_main] +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // `data` allegedly holds a legacy request message arrived from the non-secure world. + let _ = kmr_wire::legacy::deserialize_trusty_req(data); +}); diff --git a/libs/rust/wire/fuzz/fuzz_targets/message.rs b/libs/rust/wire/fuzz/fuzz_targets/message.rs new file mode 100644 index 0000000..bd59fbf --- /dev/null +++ b/libs/rust/wire/fuzz/fuzz_targets/message.rs @@ -0,0 +1,25 @@ +// Copyright 2022, 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. + +//! Fuzzer for request message parsing. + +#![no_main] +use kmr_wire::AsCborValue; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // `data` allegedly holds a CBOR-serialized request message that has arrived from the HAL + // service in userspace. Do we trust it? I don't think so... + let _ = kmr_wire::PerformOpReq::from_slice(data); +}); diff --git a/libs/rust/wire/src/keymint.rs b/libs/rust/wire/src/keymint.rs new file mode 100644 index 0000000..ec381de --- /dev/null +++ b/libs/rust/wire/src/keymint.rs @@ -0,0 +1,1226 @@ +// Copyright 2022, 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. + +//! Local types that are equivalent to those generated for KeyMint HAL interfaces +//! +//! - Enums are encoded as exhaustive Rust enums backed by `i32`, using Rust naming +//! conventions (CamelCase values). +//! - Structs have all fields `pub`, using Rust naming conventions (snake_case fields). +//! - Both enums and structs get a `[derive(AsCborValue)]` +//! +//! Special cases: +//! - The `BeginResult` type of the HAL interface is omitted here, as it includes a +//! Binder reference. +//! - `Tag` is private to this module, because.... +//! - `KeyParam` is a Rust `enum` that is used in place of the `KeyParameter` struct, meaning... +//! - `KeyParameterValue` is not included here. + +use crate::{ + cbor, cbor_type_error, try_from_n, vec_try, AsCborValue, CborError, KeySizeInBits, RsaExponent, +}; +use enumn::N; +use kmr_derive::{AsCborValue, FromRawTag}; + +/// Default certificate serial number of 1. +pub const DEFAULT_CERT_SERIAL: &[u8] = &[0x01]; + +/// ASN.1 DER encoding of the default certificate subject of 'CN=Android Keystore Key'. +pub const DEFAULT_CERT_SUBJECT: &[u8] = &[ + 0x30, 0x1f, // SEQUENCE len 31 + 0x31, 0x1d, // SET len 29 + 0x30, 0x1b, // SEQUENCE len 27 + 0x06, 0x03, // OBJECT IDENTIFIER len 3 + 0x55, 0x04, 0x03, // 2.5.4.3 (commonName) + 0x0c, 0x14, // UTF8String len 20 + 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x4b, 0x65, 0x79, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x20, 0x4b, 0x65, 0x79, // "Android Keystore Key" +]; + +/// Constants to indicate whether or not to include/expect more messages when splitting and then +/// assembling the large responses sent from the TA to the HAL. +pub const NEXT_MESSAGE_SIGNAL_TRUE: u8 = 0b00000001u8; +pub const NEXT_MESSAGE_SIGNAL_FALSE: u8 = 0b00000000u8; + +/// We use Unix epoch as the start date of an undefined certificate validity period. +pub const UNDEFINED_NOT_BEFORE: DateTime = DateTime { ms_since_epoch: 0 }; +/// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to +/// 9999-12-31T23:59:59Z. +pub const UNDEFINED_NOT_AFTER: DateTime = DateTime { ms_since_epoch: 253402300799000 }; + +/// Possible verified boot state values. +#[derive(Clone, Copy, Debug, PartialEq, Eq, N, AsCborValue)] +pub enum VerifiedBootState { + Verified = 0, + SelfSigned = 1, + Unverified = 2, + Failed = 3, +} + +impl TryFrom for VerifiedBootState { + type Error = CborError; + fn try_from(v: i32) -> Result { + Self::n(v).ok_or(CborError::OutOfRangeIntegerValue) + } +} + +/// Information provided once at start-of-day, normally by the bootloader. +/// +/// Field order is fixed, to match the CBOR type definition of `RootOfTrust` in `IKeyMintDevice`. +#[derive(Clone, Debug, AsCborValue, PartialEq, Eq)] +pub struct BootInfo { + pub verified_boot_key: Vec, + pub device_boot_locked: bool, + pub verified_boot_state: VerifiedBootState, + pub verified_boot_hash: Vec, + pub boot_patchlevel: u32, // YYYYMMDD format +} + +// Implement the `coset` CBOR serialization traits in terms of the local `AsCborValue` trait, +// in order to get access to tagged versions of serialize/deserialize. +impl coset::AsCborValue for BootInfo { + fn from_cbor_value(value: cbor::value::Value) -> coset::Result { + ::from_cbor_value(value).map_err(|e| e.into()) + } + fn to_cbor_value(self) -> coset::Result { + ::to_cbor_value(self).map_err(|e| e.into()) + } +} + +impl coset::TaggedCborSerializable for BootInfo { + const TAG: u64 = 40001; +} + +/// Representation of a date/time. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct DateTime { + pub ms_since_epoch: i64, +} + +impl AsCborValue for DateTime { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let val = ::from_cbor_value(value)?; + Ok(Self { ms_since_epoch: val }) + } + fn to_cbor_value(self) -> Result { + self.ms_since_epoch.to_cbor_value() + } + fn cddl_typename() -> Option { + Some("DateTime".to_string()) + } + fn cddl_schema() -> Option { + Some("int".to_string()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum Algorithm { + Rsa = 1, + Ec = 3, + Aes = 32, + TripleDes = 33, + Hmac = 128, +} +try_from_n!(Algorithm); + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct AttestationKey { + pub key_blob: Vec, + pub attest_key_params: Vec, + pub issuer_subject_name: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum BlockMode { + Ecb = 1, + Cbc = 2, + Ctr = 3, + Gcm = 32, +} +try_from_n!(BlockMode); + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct Certificate { + pub encoded_certificate: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum Digest { + None = 0, + Md5 = 1, + Sha1 = 2, + Sha224 = 3, + Sha256 = 4, + Sha384 = 5, + Sha512 = 6, +} +try_from_n!(Digest); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum EcCurve { + P224 = 0, + P256 = 1, + P384 = 2, + P521 = 3, + #[cfg(feature = "hal_v2")] + Curve25519 = 4, +} +try_from_n!(EcCurve); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum ErrorCode { + Ok = 0, + RootOfTrustAlreadySet = -1, + UnsupportedPurpose = -2, + IncompatiblePurpose = -3, + UnsupportedAlgorithm = -4, + IncompatibleAlgorithm = -5, + UnsupportedKeySize = -6, + UnsupportedBlockMode = -7, + IncompatibleBlockMode = -8, + UnsupportedMacLength = -9, + UnsupportedPaddingMode = -10, + IncompatiblePaddingMode = -11, + UnsupportedDigest = -12, + IncompatibleDigest = -13, + InvalidExpirationTime = -14, + InvalidUserId = -15, + InvalidAuthorizationTimeout = -16, + UnsupportedKeyFormat = -17, + IncompatibleKeyFormat = -18, + UnsupportedKeyEncryptionAlgorithm = -19, + UnsupportedKeyVerificationAlgorithm = -20, + InvalidInputLength = -21, + KeyExportOptionsInvalid = -22, + DelegationNotAllowed = -23, + KeyNotYetValid = -24, + KeyExpired = -25, + KeyUserNotAuthenticated = -26, + OutputParameterNull = -27, + InvalidOperationHandle = -28, + InsufficientBufferSpace = -29, + VerificationFailed = -30, + TooManyOperations = -31, + UnexpectedNullPointer = -32, + InvalidKeyBlob = -33, + ImportedKeyNotEncrypted = -34, + ImportedKeyDecryptionFailed = -35, + ImportedKeyNotSigned = -36, + ImportedKeyVerificationFailed = -37, + InvalidArgument = -38, + UnsupportedTag = -39, + InvalidTag = -40, + MemoryAllocationFailed = -41, + ImportParameterMismatch = -44, + SecureHwAccessDenied = -45, + OperationCancelled = -46, + ConcurrentAccessConflict = -47, + SecureHwBusy = -48, + SecureHwCommunicationFailed = -49, + UnsupportedEcField = -50, + MissingNonce = -51, + InvalidNonce = -52, + MissingMacLength = -53, + KeyRateLimitExceeded = -54, + CallerNonceProhibited = -55, + KeyMaxOpsExceeded = -56, + InvalidMacLength = -57, + MissingMinMacLength = -58, + UnsupportedMinMacLength = -59, + UnsupportedKdf = -60, + UnsupportedEcCurve = -61, + KeyRequiresUpgrade = -62, + AttestationChallengeMissing = -63, + KeymintNotConfigured = -64, + AttestationApplicationIdMissing = -65, + CannotAttestIds = -66, + RollbackResistanceUnavailable = -67, + HardwareTypeUnavailable = -68, + ProofOfPresenceRequired = -69, + ConcurrentProofOfPresenceRequested = -70, + NoUserConfirmation = -71, + DeviceLocked = -72, + EarlyBootEnded = -73, + AttestationKeysNotProvisioned = -74, + AttestationIdsNotProvisioned = -75, + InvalidOperation = -76, + StorageKeyUnsupported = -77, + IncompatibleMgfDigest = -78, + UnsupportedMgfDigest = -79, + MissingNotBefore = -80, + MissingNotAfter = -81, + MissingIssuerSubject = -82, + InvalidIssuerSubject = -83, + BootLevelExceeded = -84, + HardwareNotYetAvailable = -85, + ModuleHashAlreadySet = -86, + Unimplemented = -100, + VersionMismatch = -101, + UnknownError = -1000, + // Implementer's namespace for error codes starts at -10000. + EncodingError = -20000, + BoringSslError = -30000, +} +try_from_n!(ErrorCode); + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct HardwareAuthToken { + pub challenge: i64, + pub user_id: i64, + pub authenticator_id: i64, + pub authenticator_type: HardwareAuthenticatorType, + pub timestamp: super::secureclock::Timestamp, + pub mac: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum HardwareAuthenticatorType { + None = 0, + Password = 1, + Fingerprint = 2, + Any = -1, +} +try_from_n!(HardwareAuthenticatorType); + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct KeyCharacteristics { + pub security_level: SecurityLevel, + pub authorizations: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct KeyCreationResult { + pub key_blob: Vec, + pub key_characteristics: Vec, + pub certificate_chain: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum KeyFormat { + X509 = 0, + Pkcs8 = 1, + Raw = 3, +} +try_from_n!(KeyFormat); + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct KeyMintHardwareInfo { + pub version_number: i32, + pub security_level: SecurityLevel, + pub key_mint_name: String, + pub key_mint_author_name: String, + pub timestamp_token_required: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum KeyOrigin { + Generated = 0, + Derived = 1, + Imported = 2, + Reserved = 3, + SecurelyImported = 4, +} +try_from_n!(KeyOrigin); + +/// Rust exhaustive enum for all key parameters. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum KeyParam { + Purpose(KeyPurpose), + Algorithm(Algorithm), + KeySize(KeySizeInBits), + BlockMode(BlockMode), + Digest(Digest), + Padding(PaddingMode), + CallerNonce, + MinMacLength(u32), + EcCurve(EcCurve), + RsaPublicExponent(RsaExponent), + IncludeUniqueId, + RsaOaepMgfDigest(Digest), + BootloaderOnly, + RollbackResistance, + EarlyBootOnly, + ActiveDatetime(DateTime), + OriginationExpireDatetime(DateTime), + UsageExpireDatetime(DateTime), + MaxUsesPerBoot(u32), + UsageCountLimit(u32), + UserId(u32), + UserSecureId(u64), + NoAuthRequired, + UserAuthType(u32), + AuthTimeout(u32), + AllowWhileOnBody, + TrustedUserPresenceRequired, + TrustedConfirmationRequired, + UnlockedDeviceRequired, + ApplicationId(Vec), + ApplicationData(Vec), + CreationDatetime(DateTime), + Origin(KeyOrigin), + RootOfTrust(Vec), + OsVersion(u32), + OsPatchlevel(u32), + AttestationChallenge(Vec), + AttestationApplicationId(Vec), + AttestationIdBrand(Vec), + AttestationIdDevice(Vec), + AttestationIdProduct(Vec), + AttestationIdSerial(Vec), + AttestationIdImei(Vec), + #[cfg(feature = "hal_v3")] + AttestationIdSecondImei(Vec), + AttestationIdMeid(Vec), + AttestationIdManufacturer(Vec), + AttestationIdModel(Vec), + VendorPatchlevel(u32), + BootPatchlevel(u32), + DeviceUniqueAttestation, + StorageKey, + Nonce(Vec), + MacLength(u32), + ResetSinceIdRotation, + CertificateSerial(Vec), + CertificateSubject(Vec), + CertificateNotBefore(DateTime), + CertificateNotAfter(DateTime), + MaxBootLevel(u32), + #[cfg(feature = "hal_v4")] + ModuleHash(Vec), +} + +impl KeyParam { + pub fn tag(&self) -> Tag { + match self { + KeyParam::Algorithm(_) => Tag::Algorithm, + KeyParam::BlockMode(_) => Tag::BlockMode, + KeyParam::Padding(_) => Tag::Padding, + KeyParam::Digest(_) => Tag::Digest, + KeyParam::EcCurve(_) => Tag::EcCurve, + KeyParam::Origin(_) => Tag::Origin, + KeyParam::Purpose(_) => Tag::Purpose, + KeyParam::KeySize(_) => Tag::KeySize, + KeyParam::CallerNonce => Tag::CallerNonce, + KeyParam::MinMacLength(_) => Tag::MinMacLength, + KeyParam::RsaPublicExponent(_) => Tag::RsaPublicExponent, + KeyParam::IncludeUniqueId => Tag::IncludeUniqueId, + KeyParam::RsaOaepMgfDigest(_) => Tag::RsaOaepMgfDigest, + KeyParam::BootloaderOnly => Tag::BootloaderOnly, + KeyParam::RollbackResistance => Tag::RollbackResistance, + KeyParam::EarlyBootOnly => Tag::EarlyBootOnly, + KeyParam::ActiveDatetime(_) => Tag::ActiveDatetime, + KeyParam::OriginationExpireDatetime(_) => Tag::OriginationExpireDatetime, + KeyParam::UsageExpireDatetime(_) => Tag::UsageExpireDatetime, + KeyParam::MaxUsesPerBoot(_) => Tag::MaxUsesPerBoot, + KeyParam::UsageCountLimit(_) => Tag::UsageCountLimit, + KeyParam::UserId(_) => Tag::UserId, + KeyParam::UserSecureId(_) => Tag::UserSecureId, + KeyParam::NoAuthRequired => Tag::NoAuthRequired, + KeyParam::UserAuthType(_) => Tag::UserAuthType, + KeyParam::AuthTimeout(_) => Tag::AuthTimeout, + KeyParam::AllowWhileOnBody => Tag::AllowWhileOnBody, + KeyParam::TrustedUserPresenceRequired => Tag::TrustedUserPresenceRequired, + KeyParam::TrustedConfirmationRequired => Tag::TrustedConfirmationRequired, + KeyParam::UnlockedDeviceRequired => Tag::UnlockedDeviceRequired, + KeyParam::ApplicationId(_) => Tag::ApplicationId, + KeyParam::ApplicationData(_) => Tag::ApplicationData, + KeyParam::CreationDatetime(_) => Tag::CreationDatetime, + KeyParam::RootOfTrust(_) => Tag::RootOfTrust, + KeyParam::OsVersion(_) => Tag::OsVersion, + KeyParam::OsPatchlevel(_) => Tag::OsPatchlevel, + KeyParam::AttestationChallenge(_) => Tag::AttestationChallenge, + KeyParam::AttestationApplicationId(_) => Tag::AttestationApplicationId, + KeyParam::AttestationIdBrand(_) => Tag::AttestationIdBrand, + KeyParam::AttestationIdDevice(_) => Tag::AttestationIdDevice, + KeyParam::AttestationIdProduct(_) => Tag::AttestationIdProduct, + KeyParam::AttestationIdSerial(_) => Tag::AttestationIdSerial, + KeyParam::AttestationIdImei(_) => Tag::AttestationIdImei, + #[cfg(feature = "hal_v3")] + KeyParam::AttestationIdSecondImei(_) => Tag::AttestationIdSecondImei, + KeyParam::AttestationIdMeid(_) => Tag::AttestationIdMeid, + KeyParam::AttestationIdManufacturer(_) => Tag::AttestationIdManufacturer, + KeyParam::AttestationIdModel(_) => Tag::AttestationIdModel, + KeyParam::VendorPatchlevel(_) => Tag::VendorPatchlevel, + KeyParam::BootPatchlevel(_) => Tag::BootPatchlevel, + KeyParam::DeviceUniqueAttestation => Tag::DeviceUniqueAttestation, + KeyParam::StorageKey => Tag::StorageKey, + KeyParam::Nonce(_) => Tag::Nonce, + KeyParam::MacLength(_) => Tag::MacLength, + KeyParam::ResetSinceIdRotation => Tag::ResetSinceIdRotation, + KeyParam::CertificateSerial(_) => Tag::CertificateSerial, + KeyParam::CertificateSubject(_) => Tag::CertificateSubject, + KeyParam::CertificateNotBefore(_) => Tag::CertificateNotBefore, + KeyParam::CertificateNotAfter(_) => Tag::CertificateNotAfter, + KeyParam::MaxBootLevel(_) => Tag::MaxBootLevel, + #[cfg(feature = "hal_v4")] + KeyParam::ModuleHash(_) => Tag::ModuleHash, + } + } +} + +/// Check that a `bool` value is true (false values are represented by the absence of a tag). +fn check_bool(value: cbor::value::Value) -> Result<(), crate::CborError> { + match value { + cbor::value::Value::Bool(true) => Ok(()), + cbor::value::Value::Bool(false) => Err(crate::CborError::UnexpectedItem("false", "true")), + _ => crate::cbor_type_error(&value, "true"), + } +} + +/// Manual implementation of [`crate::AsCborValue`] for the [`KeyParam`] enum that +/// matches the serialization of the HAL `Tag` / `KeyParameterValue` types. +impl crate::AsCborValue for KeyParam { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let mut a = match value { + cbor::value::Value::Array(a) => a, + _ => return crate::cbor_type_error(&value, "arr"), + }; + if a.len() != 2 { + return Err(crate::CborError::UnexpectedItem("arr", "arr len 2")); + } + + // Need to know the tag value to completely parse the value. + let raw = a.remove(1); + let tag = ::from_cbor_value(a.remove(0))?; + + Ok(match tag { + Tag::Algorithm => KeyParam::Algorithm(::from_cbor_value(raw)?), + Tag::BlockMode => KeyParam::BlockMode(::from_cbor_value(raw)?), + Tag::Padding => KeyParam::Padding(::from_cbor_value(raw)?), + Tag::Digest => KeyParam::Digest(::from_cbor_value(raw)?), + Tag::EcCurve => KeyParam::EcCurve(::from_cbor_value(raw)?), + Tag::Origin => KeyParam::Origin(::from_cbor_value(raw)?), + Tag::Purpose => KeyParam::Purpose(::from_cbor_value(raw)?), + Tag::KeySize => KeyParam::KeySize(::from_cbor_value(raw)?), + Tag::CallerNonce => KeyParam::CallerNonce, + Tag::MinMacLength => KeyParam::MinMacLength(::from_cbor_value(raw)?), + Tag::RsaPublicExponent => { + KeyParam::RsaPublicExponent(::from_cbor_value(raw)?) + } + Tag::IncludeUniqueId => { + check_bool(raw)?; + KeyParam::IncludeUniqueId + } + Tag::RsaOaepMgfDigest => KeyParam::RsaOaepMgfDigest(::from_cbor_value(raw)?), + Tag::BootloaderOnly => { + check_bool(raw)?; + KeyParam::BootloaderOnly + } + Tag::RollbackResistance => { + check_bool(raw)?; + KeyParam::RollbackResistance + } + Tag::EarlyBootOnly => { + check_bool(raw)?; + KeyParam::EarlyBootOnly + } + Tag::ActiveDatetime => KeyParam::ActiveDatetime(::from_cbor_value(raw)?), + Tag::OriginationExpireDatetime => { + KeyParam::OriginationExpireDatetime(::from_cbor_value(raw)?) + } + Tag::UsageExpireDatetime => { + KeyParam::UsageExpireDatetime(::from_cbor_value(raw)?) + } + Tag::MaxUsesPerBoot => KeyParam::MaxUsesPerBoot(::from_cbor_value(raw)?), + Tag::UsageCountLimit => KeyParam::UsageCountLimit(::from_cbor_value(raw)?), + Tag::UserId => KeyParam::UserId(::from_cbor_value(raw)?), + Tag::UserSecureId => KeyParam::UserSecureId(::from_cbor_value(raw)?), + Tag::NoAuthRequired => { + check_bool(raw)?; + KeyParam::NoAuthRequired + } + Tag::UserAuthType => KeyParam::UserAuthType(::from_cbor_value(raw)?), + Tag::AuthTimeout => KeyParam::AuthTimeout(::from_cbor_value(raw)?), + Tag::AllowWhileOnBody => KeyParam::AllowWhileOnBody, + Tag::TrustedUserPresenceRequired => { + check_bool(raw)?; + KeyParam::TrustedUserPresenceRequired + } + Tag::TrustedConfirmationRequired => { + check_bool(raw)?; + KeyParam::TrustedConfirmationRequired + } + Tag::UnlockedDeviceRequired => { + check_bool(raw)?; + KeyParam::UnlockedDeviceRequired + } + Tag::ApplicationId => KeyParam::ApplicationId(>::from_cbor_value(raw)?), + Tag::ApplicationData => KeyParam::ApplicationData(>::from_cbor_value(raw)?), + Tag::CreationDatetime => KeyParam::CreationDatetime(::from_cbor_value(raw)?), + Tag::RootOfTrust => KeyParam::RootOfTrust(>::from_cbor_value(raw)?), + Tag::OsVersion => KeyParam::OsVersion(::from_cbor_value(raw)?), + Tag::OsPatchlevel => KeyParam::OsPatchlevel(::from_cbor_value(raw)?), + Tag::AttestationChallenge => { + KeyParam::AttestationChallenge(>::from_cbor_value(raw)?) + } + Tag::AttestationApplicationId => { + KeyParam::AttestationApplicationId(>::from_cbor_value(raw)?) + } + Tag::AttestationIdBrand => { + KeyParam::AttestationIdBrand(>::from_cbor_value(raw)?) + } + Tag::AttestationIdDevice => { + KeyParam::AttestationIdDevice(>::from_cbor_value(raw)?) + } + Tag::AttestationIdProduct => { + KeyParam::AttestationIdProduct(>::from_cbor_value(raw)?) + } + Tag::AttestationIdSerial => { + KeyParam::AttestationIdSerial(>::from_cbor_value(raw)?) + } + Tag::AttestationIdImei => KeyParam::AttestationIdImei(>::from_cbor_value(raw)?), + #[cfg(feature = "hal_v3")] + Tag::AttestationIdSecondImei => { + KeyParam::AttestationIdSecondImei(>::from_cbor_value(raw)?) + } + Tag::AttestationIdMeid => KeyParam::AttestationIdMeid(>::from_cbor_value(raw)?), + Tag::AttestationIdManufacturer => { + KeyParam::AttestationIdManufacturer(>::from_cbor_value(raw)?) + } + Tag::AttestationIdModel => { + KeyParam::AttestationIdModel(>::from_cbor_value(raw)?) + } + Tag::VendorPatchlevel => KeyParam::VendorPatchlevel(::from_cbor_value(raw)?), + Tag::BootPatchlevel => KeyParam::BootPatchlevel(::from_cbor_value(raw)?), + Tag::DeviceUniqueAttestation => { + check_bool(raw)?; + KeyParam::DeviceUniqueAttestation + } + Tag::StorageKey => KeyParam::StorageKey, + Tag::Nonce => KeyParam::Nonce(>::from_cbor_value(raw)?), + Tag::MacLength => KeyParam::MacLength(::from_cbor_value(raw)?), + Tag::ResetSinceIdRotation => { + check_bool(raw)?; + KeyParam::ResetSinceIdRotation + } + Tag::CertificateSerial => KeyParam::CertificateSerial(>::from_cbor_value(raw)?), + Tag::CertificateSubject => { + KeyParam::CertificateSubject(>::from_cbor_value(raw)?) + } + Tag::CertificateNotBefore => { + KeyParam::CertificateNotBefore(::from_cbor_value(raw)?) + } + Tag::CertificateNotAfter => { + KeyParam::CertificateNotAfter(::from_cbor_value(raw)?) + } + Tag::MaxBootLevel => KeyParam::MaxBootLevel(::from_cbor_value(raw)?), + #[cfg(feature = "hal_v4")] + Tag::ModuleHash => KeyParam::ModuleHash(>::from_cbor_value(raw)?), + + _ => return Err(crate::CborError::UnexpectedItem("tag", "known tag")), + }) + } + fn to_cbor_value(self) -> Result { + let (tag, val) = match self { + KeyParam::Algorithm(v) => (Tag::Algorithm, v.to_cbor_value()?), + KeyParam::BlockMode(v) => (Tag::BlockMode, v.to_cbor_value()?), + KeyParam::Padding(v) => (Tag::Padding, v.to_cbor_value()?), + KeyParam::Digest(v) => (Tag::Digest, v.to_cbor_value()?), + KeyParam::EcCurve(v) => (Tag::EcCurve, v.to_cbor_value()?), + KeyParam::Origin(v) => (Tag::Origin, v.to_cbor_value()?), + KeyParam::Purpose(v) => (Tag::Purpose, v.to_cbor_value()?), + KeyParam::KeySize(v) => (Tag::KeySize, v.to_cbor_value()?), + KeyParam::CallerNonce => (Tag::CallerNonce, true.to_cbor_value()?), + KeyParam::MinMacLength(v) => (Tag::MinMacLength, v.to_cbor_value()?), + KeyParam::RsaPublicExponent(v) => (Tag::RsaPublicExponent, v.to_cbor_value()?), + KeyParam::IncludeUniqueId => (Tag::IncludeUniqueId, true.to_cbor_value()?), + KeyParam::RsaOaepMgfDigest(v) => (Tag::RsaOaepMgfDigest, v.to_cbor_value()?), + KeyParam::BootloaderOnly => (Tag::BootloaderOnly, true.to_cbor_value()?), + KeyParam::RollbackResistance => (Tag::RollbackResistance, true.to_cbor_value()?), + KeyParam::EarlyBootOnly => (Tag::EarlyBootOnly, true.to_cbor_value()?), + KeyParam::ActiveDatetime(v) => (Tag::ActiveDatetime, v.to_cbor_value()?), + KeyParam::OriginationExpireDatetime(v) => { + (Tag::OriginationExpireDatetime, v.to_cbor_value()?) + } + KeyParam::UsageExpireDatetime(v) => (Tag::UsageExpireDatetime, v.to_cbor_value()?), + KeyParam::MaxUsesPerBoot(v) => (Tag::MaxUsesPerBoot, v.to_cbor_value()?), + KeyParam::UsageCountLimit(v) => (Tag::UsageCountLimit, v.to_cbor_value()?), + KeyParam::UserId(v) => (Tag::UserId, v.to_cbor_value()?), + KeyParam::UserSecureId(v) => (Tag::UserSecureId, v.to_cbor_value()?), + KeyParam::NoAuthRequired => (Tag::NoAuthRequired, true.to_cbor_value()?), + KeyParam::UserAuthType(v) => (Tag::UserAuthType, v.to_cbor_value()?), + KeyParam::AuthTimeout(v) => (Tag::AuthTimeout, v.to_cbor_value()?), + KeyParam::AllowWhileOnBody => (Tag::AllowWhileOnBody, true.to_cbor_value()?), + KeyParam::TrustedUserPresenceRequired => { + (Tag::TrustedUserPresenceRequired, true.to_cbor_value()?) + } + KeyParam::TrustedConfirmationRequired => { + (Tag::TrustedConfirmationRequired, true.to_cbor_value()?) + } + KeyParam::UnlockedDeviceRequired => { + (Tag::UnlockedDeviceRequired, true.to_cbor_value()?) + } + KeyParam::ApplicationId(v) => (Tag::ApplicationId, v.to_cbor_value()?), + KeyParam::ApplicationData(v) => (Tag::ApplicationData, v.to_cbor_value()?), + KeyParam::CreationDatetime(v) => (Tag::CreationDatetime, v.to_cbor_value()?), + KeyParam::RootOfTrust(v) => (Tag::RootOfTrust, v.to_cbor_value()?), + KeyParam::OsVersion(v) => (Tag::OsVersion, v.to_cbor_value()?), + KeyParam::OsPatchlevel(v) => (Tag::OsPatchlevel, v.to_cbor_value()?), + KeyParam::AttestationChallenge(v) => (Tag::AttestationChallenge, v.to_cbor_value()?), + KeyParam::AttestationApplicationId(v) => { + (Tag::AttestationApplicationId, v.to_cbor_value()?) + } + KeyParam::AttestationIdBrand(v) => (Tag::AttestationIdBrand, v.to_cbor_value()?), + KeyParam::AttestationIdDevice(v) => (Tag::AttestationIdDevice, v.to_cbor_value()?), + KeyParam::AttestationIdProduct(v) => (Tag::AttestationIdProduct, v.to_cbor_value()?), + KeyParam::AttestationIdSerial(v) => (Tag::AttestationIdSerial, v.to_cbor_value()?), + KeyParam::AttestationIdImei(v) => (Tag::AttestationIdImei, v.to_cbor_value()?), + #[cfg(feature = "hal_v3")] + KeyParam::AttestationIdSecondImei(v) => { + (Tag::AttestationIdSecondImei, v.to_cbor_value()?) + } + KeyParam::AttestationIdMeid(v) => (Tag::AttestationIdMeid, v.to_cbor_value()?), + KeyParam::AttestationIdManufacturer(v) => { + (Tag::AttestationIdManufacturer, v.to_cbor_value()?) + } + KeyParam::AttestationIdModel(v) => (Tag::AttestationIdModel, v.to_cbor_value()?), + KeyParam::VendorPatchlevel(v) => (Tag::VendorPatchlevel, v.to_cbor_value()?), + KeyParam::BootPatchlevel(v) => (Tag::BootPatchlevel, v.to_cbor_value()?), + KeyParam::DeviceUniqueAttestation => { + (Tag::DeviceUniqueAttestation, true.to_cbor_value()?) + } + KeyParam::StorageKey => (Tag::StorageKey, true.to_cbor_value()?), + KeyParam::Nonce(v) => (Tag::Nonce, v.to_cbor_value()?), + KeyParam::MacLength(v) => (Tag::MacLength, v.to_cbor_value()?), + KeyParam::ResetSinceIdRotation => (Tag::ResetSinceIdRotation, true.to_cbor_value()?), + KeyParam::CertificateSerial(v) => (Tag::CertificateSerial, v.to_cbor_value()?), + KeyParam::CertificateSubject(v) => (Tag::CertificateSubject, v.to_cbor_value()?), + KeyParam::CertificateNotBefore(v) => (Tag::CertificateNotBefore, v.to_cbor_value()?), + KeyParam::CertificateNotAfter(v) => (Tag::CertificateNotAfter, v.to_cbor_value()?), + KeyParam::MaxBootLevel(v) => (Tag::MaxBootLevel, v.to_cbor_value()?), + #[cfg(feature = "hal_v4")] + KeyParam::ModuleHash(v) => (Tag::ModuleHash, v.to_cbor_value()?), + }; + Ok(cbor::value::Value::Array(vec_try![tag.to_cbor_value()?, val]?)) + } + fn cddl_typename() -> Option { + Some("KeyParam".to_string()) + } + fn cddl_schema() -> Option { + let mut result = "&(\n".to_string(); + + result += &format!( + " [{}, {}], ; {}\n", + Tag::Algorithm as i32, + Algorithm::cddl_ref(), + "Tag_Algorithm" + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::BlockMode as i32, + BlockMode::cddl_ref(), + "Tag_BlockMode", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::Padding as i32, + PaddingMode::cddl_ref(), + "Tag_Padding", + ); + result += + &format!(" [{}, {}], ; {}\n", Tag::Digest as i32, Digest::cddl_ref(), "Tag_Digest",); + result += &format!( + " [{}, {}], ; {}\n", + Tag::EcCurve as i32, + EcCurve::cddl_ref(), + "Tag_EcCurve", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::Origin as i32, + KeyOrigin::cddl_ref(), + "Tag_Origin", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::Purpose as i32, + KeyPurpose::cddl_ref(), + "Tag_Purpose", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::KeySize as i32, + KeySizeInBits::cddl_ref(), + "Tag_KeySize", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::CallerNonce as i32, + Vec::::cddl_ref(), + "Tag_CallerNonce", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::MinMacLength as i32, + u32::cddl_ref(), + "Tag_MinMacLength", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::RsaPublicExponent as i32, + RsaExponent::cddl_ref(), + "Tag_RsaPublicExponent", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::IncludeUniqueId as i32, + "true", + "Tag_IncludeUniqueId", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::RsaOaepMgfDigest as i32, + Digest::cddl_ref(), + "Tag_RsaOaepMgfDigest", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::BootloaderOnly as i32, + "true", + "Tag_BootloaderOnly", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::RollbackResistance as i32, + "true", + "Tag_RollbackResistance", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::EarlyBootOnly as i32, + "true", + "Tag_EarlyBootOnly", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::ActiveDatetime as i32, + DateTime::cddl_ref(), + "Tag_ActiveDatetime", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::OriginationExpireDatetime as i32, + DateTime::cddl_ref(), + "Tag_OriginationExpireDatetime", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::UsageExpireDatetime as i32, + DateTime::cddl_ref(), + "Tag_UsageExpireDatetime", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::MaxUsesPerBoot as i32, + u32::cddl_ref(), + "Tag_MaxUsesPerBoot", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::UsageCountLimit as i32, + u32::cddl_ref(), + "Tag_UsageCountLimit", + ); + result += + &format!(" [{}, {}], ; {}\n", Tag::UserId as i32, u32::cddl_ref(), "Tag_UserId",); + result += &format!( + " [{}, {}], ; {}\n", + Tag::UserSecureId as i32, + u64::cddl_ref(), + "Tag_UserSecureId", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::NoAuthRequired as i32, + "true", + "Tag_NoAuthRequired", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::UserAuthType as i32, + u32::cddl_ref(), + "Tag_UserAuthType", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AuthTimeout as i32, + u32::cddl_ref(), + "Tag_AuthTimeout", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AllowWhileOnBody as i32, + "true", + "Tag_AllowWhileOnBody", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::TrustedUserPresenceRequired as i32, + "true", + "Tag_TrustedUserPresenceRequired", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::TrustedConfirmationRequired as i32, + "true", + "Tag_TrustedConfirmationRequired", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::UnlockedDeviceRequired as i32, + "true", + "Tag_UnlockedDeviceRequired", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::ApplicationId as i32, + Vec::::cddl_ref(), + "Tag_ApplicationId", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::ApplicationData as i32, + Vec::::cddl_ref(), + "Tag_ApplicationData", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::CreationDatetime as i32, + DateTime::cddl_ref(), + "Tag_CreationDatetime", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::RootOfTrust as i32, + Vec::::cddl_ref(), + "Tag_RootOfTrust", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::OsVersion as i32, + u32::cddl_ref(), + "Tag_OsVersion", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::OsPatchlevel as i32, + u32::cddl_ref(), + "Tag_OsPatchlevel", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationChallenge as i32, + Vec::::cddl_ref(), + "Tag_AttestationChallenge", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationApplicationId as i32, + Vec::::cddl_ref(), + "Tag_AttestationApplicationId", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdBrand as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdBrand", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdDevice as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdDevice", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdProduct as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdProduct", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdSerial as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdSerial", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdImei as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdImei", + ); + #[cfg(feature = "hal_v3")] + { + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdSecondImei as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdSecondImei", + ); + } + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdMeid as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdMeid", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdManufacturer as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdManufacturer", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::AttestationIdModel as i32, + Vec::::cddl_ref(), + "Tag_AttestationIdModel", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::VendorPatchlevel as i32, + u32::cddl_ref(), + "Tag_VendorPatchlevel", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::BootPatchlevel as i32, + u32::cddl_ref(), + "Tag_BootPatchlevel", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::DeviceUniqueAttestation as i32, + "true", + "Tag_DeviceUniqueAttestation", + ); + result += + &format!(" [{}, {}], ; {}\n", Tag::StorageKey as i32, "true", "Tag_StorageKey",); + result += &format!( + " [{}, {}], ; {}\n", + Tag::Nonce as i32, + Vec::::cddl_ref(), + "Tag_Nonce", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::MacLength as i32, + u32::cddl_ref(), + "Tag_MacLength", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::ResetSinceIdRotation as i32, + "true", + "Tag_ResetSinceIdRotation", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::CertificateSerial as i32, + Vec::::cddl_ref(), + "Tag_CertificateSerial", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::CertificateSubject as i32, + Vec::::cddl_ref(), + "Tag_CertificateSubject", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::CertificateNotBefore as i32, + DateTime::cddl_ref(), + "Tag_CertificateNotBefore", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::CertificateNotAfter as i32, + DateTime::cddl_ref(), + "Tag_CertificateNotAfter", + ); + result += &format!( + " [{}, {}], ; {}\n", + Tag::MaxBootLevel as i32, + u32::cddl_ref(), + "Tag_MaxBootLevel", + ); + #[cfg(feature = "hal_v4")] + { + result += &format!( + " [{}, {}], ; {}\n", + Tag::ModuleHash as i32, + Vec::::cddl_ref(), + "Tag_ModuleHash", + ); + } + result += ")"; + Some(result) + } +} + +/// Determine the tag type for a tag, based on the top 4 bits of the tag number. +pub fn tag_type(tag: Tag) -> TagType { + match ((tag as u32) & 0xf0000000u32) as i32 { + x if x == TagType::Enum as i32 => TagType::Enum, + x if x == TagType::EnumRep as i32 => TagType::EnumRep, + x if x == TagType::Uint as i32 => TagType::Uint, + x if x == TagType::UintRep as i32 => TagType::UintRep, + x if x == TagType::Ulong as i32 => TagType::Ulong, + x if x == TagType::Date as i32 => TagType::Date, + x if x == TagType::Bool as i32 => TagType::Bool, + x if x == TagType::Bignum as i32 => TagType::Bignum, + x if x == TagType::Bytes as i32 => TagType::Bytes, + x if x == TagType::UlongRep as i32 => TagType::UlongRep, + _ => TagType::Invalid, + } +} + +/// Determine the raw tag value with tag type information stripped out. +pub fn raw_tag_value(tag: Tag) -> u32 { + (tag as u32) & 0x0fffffffu32 +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum KeyPurpose { + Encrypt = 0, + Decrypt = 1, + Sign = 2, + Verify = 3, + WrapKey = 5, + AgreeKey = 6, + AttestKey = 7, +} +try_from_n!(KeyPurpose); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum PaddingMode { + None = 1, + RsaOaep = 2, + RsaPss = 3, + RsaPkcs115Encrypt = 4, + RsaPkcs115Sign = 5, + Pkcs7 = 64, +} +try_from_n!(PaddingMode); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum SecurityLevel { + Software = 0, + TrustedEnvironment = 1, + Strongbox = 2, + Keystore = 100, +} +try_from_n!(SecurityLevel); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, FromRawTag, N)] +#[repr(i32)] +pub enum Tag { + Invalid = 0, + Purpose = 536870913, + Algorithm = 268435458, + KeySize = 805306371, + BlockMode = 536870916, + Digest = 536870917, + Padding = 536870918, + CallerNonce = 1879048199, + MinMacLength = 805306376, + EcCurve = 268435466, + RsaPublicExponent = 1342177480, + IncludeUniqueId = 1879048394, + RsaOaepMgfDigest = 536871115, + BootloaderOnly = 1879048494, + RollbackResistance = 1879048495, + HardwareType = 268435760, + EarlyBootOnly = 1879048497, + ActiveDatetime = 1610613136, + OriginationExpireDatetime = 1610613137, + UsageExpireDatetime = 1610613138, + MinSecondsBetweenOps = 805306771, + MaxUsesPerBoot = 805306772, + UsageCountLimit = 805306773, + UserId = 805306869, + UserSecureId = -1610612234, + NoAuthRequired = 1879048695, + UserAuthType = 268435960, + AuthTimeout = 805306873, + AllowWhileOnBody = 1879048698, + TrustedUserPresenceRequired = 1879048699, + TrustedConfirmationRequired = 1879048700, + UnlockedDeviceRequired = 1879048701, + ApplicationId = -1879047591, + ApplicationData = -1879047492, + CreationDatetime = 1610613437, + Origin = 268436158, + RootOfTrust = -1879047488, + OsVersion = 805307073, + OsPatchlevel = 805307074, + UniqueId = -1879047485, + AttestationChallenge = -1879047484, + AttestationApplicationId = -1879047483, + AttestationIdBrand = -1879047482, + AttestationIdDevice = -1879047481, + AttestationIdProduct = -1879047480, + AttestationIdSerial = -1879047479, + AttestationIdImei = -1879047478, + AttestationIdMeid = -1879047477, + AttestationIdManufacturer = -1879047476, + AttestationIdModel = -1879047475, + VendorPatchlevel = 805307086, + BootPatchlevel = 805307087, + DeviceUniqueAttestation = 1879048912, + IdentityCredentialKey = 1879048913, + StorageKey = 1879048914, + #[cfg(feature = "hal_v3")] + AttestationIdSecondImei = -1879047469, + AssociatedData = -1879047192, + Nonce = -1879047191, + MacLength = 805307371, + ResetSinceIdRotation = 1879049196, + ConfirmationToken = -1879047187, + CertificateSerial = -2147482642, + CertificateSubject = -1879047185, + CertificateNotBefore = 1610613744, + CertificateNotAfter = 1610613745, + MaxBootLevel = 805307378, + #[cfg(feature = "hal_v4")] + ModuleHash = -1879047468, +} +try_from_n!(Tag); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, AsCborValue, N)] +#[repr(i32)] +pub enum TagType { + Invalid = 0, + Enum = 268435456, + EnumRep = 536870912, + Uint = 805306368, + UintRep = 1073741824, + Ulong = 1342177280, + Date = 1610612736, + Bool = 1879048192, + Bignum = -2147483648, + Bytes = -1879048192, + UlongRep = -1610612736, +} +try_from_n!(TagType); diff --git a/libs/rust/wire/src/legacy.rs b/libs/rust/wire/src/legacy.rs new file mode 100644 index 0000000..3fbc9db --- /dev/null +++ b/libs/rust/wire/src/legacy.rs @@ -0,0 +1,817 @@ +// Copyright 2022, 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. + +//! Functionality for dealing with (a subset of) legacy C++ KeyMint internal messages. +//! +//! The inner messages are defined by the classes deriving from `KeymasterMessage` in +//! `system/keymaster/include/keymaster/android_keymaster_messages.h`. Each of these classes derives +//! from `Serializable` (in `system/keymaster/include/keymaster/serializable.h`) and implements +//! `Serialize` and `Deserialize` methods that convert instances of the message into opaque +//! sequences of bytes. +//! +//! However, these opaque sequences of bytes do not self-identify which particular message is +//! involved. Instead, there is device specific code to wrap the inner serialized data into some +//! sort of envelope that identifies the message type. +//! +//! 1) For Trusty, this envelope is the `keymaster_message` struct from +//! `system/core/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h`; this struct holds +//! (and is serialized as): +//! +//! - A u32 indicating which command is involved, together with two low bits to encode whether the +//! message is a response, and a stop bit. The command code values are taken from +//! `keymaster_command` in +//! `system/core/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h`. +//! - The payload. +//! +//! 2) For Cuttlefish, this envelope is the `keymaster_message` struct from +//! `device/google/cuttlefish/common/libs/security/keymaster_channel.h`; this struct holds (and is +//! serialized as): +//! +//! - A u32 indicating which command is involved, together with a bit indicating if the message is a +//! response. The command code values are taken from `AndroidKeymasterCommand` in +//! `system/keymaster/include/keymaster/android_keymaster_messages.h`. +//! - A u32 indicating the size of the payload +//! - The payload. +//! +//! In addition to the common messages defined in `android_keymaster_messages.h`, Trusty includes +//! additional messages defined in `app/keymaster/trusty_keymaster_messages.h`. +//! +//! +//! Only a subset of legacy messages are of interest; specifically, messages that involve +//! interactions with things *other* than the HAL service, such as: +//! - The bootloader. +//! - Other TAs (e.g. Gatekeeper, ConfirmationUI) running in the secure environment. +//! - Provisioning tools. + +use crate::{ + keymint::{Algorithm, ErrorCode, VerifiedBootState}, + try_from_n, +}; +use Vec; +use enumn::N; +use kmr_derive::LegacySerialize; +use zeroize::ZeroizeOnDrop; + +/// This bit is set in the `u32` command value for response messages. +const TRUSTY_RESPONSE_BITMASK: u32 = 0x01; +/// This bit is set in the `u32` command value for the final fragment of response messages; i.e. if +/// this bit is clear on a response message, more data is expected. +pub const TRUSTY_STOP_BITMASK: u32 = 0x02; +/// The raw `u32` command value should be shifted right by this number of bits to get the command +/// enum value. +pub const TRUSTY_CMD_SHIFT: usize = 2; + +/// Legacy serialized trusty messages have as a first element the desired command encoded on a `u32` +pub const CMD_SIZE: usize = 4; +/// After the command, non-secure port responses have an error code encoded on a `u32` +pub const ERROR_CODE_SIZE: usize = 4; +/// Non-secure channel response headers are comprised of a CMD and an Error code +pub const LEGACY_NON_SEC_RSP_HEADER_SIZE: usize = CMD_SIZE + ERROR_CODE_SIZE; + +/// Key{Mint,master} version identifier. +#[derive(Debug, Clone, Copy, PartialEq, Eq, N)] +#[repr(i32)] +pub enum KmVersion { + Keymaster1 = 10, + Keymaster11 = 11, + Keymaster2 = 20, + Keymaster3 = 30, + Keymaster4 = 40, + Keymaster41 = 41, + KeyMint1 = 100, + KeyMint2 = 200, + KeyMint3 = 300, +} +try_from_n!(KmVersion); + +impl KmVersion { + /// Indicate the message format version associated with a C++ KeyMint version. + pub fn message_version(&self) -> u32 { + match self { + KmVersion::Keymaster1 => 1, + KmVersion::Keymaster11 => 2, + KmVersion::Keymaster2 + | KmVersion::Keymaster3 + | KmVersion::Keymaster4 + | KmVersion::Keymaster41 => 3, + KmVersion::KeyMint1 | KmVersion::KeyMint2 | KmVersion::KeyMint3 => 4, + } + } +} + +/// Date marker used by the last version of the previous C++ code. +pub const KM_DATE: u32 = 20201219; + +/// Errors encountered when [de-]serializing legacy messages. +#[derive(Debug, Clone, Copy)] +pub enum Error { + DataTruncated, + ExcessData(usize), + AllocationFailed, + UnexpectedResponse, + UnknownCommand(u32), + InvalidEnumValue(u32), +} + +/// Identification of Trusty messages. +pub trait TrustyMessageId { + type Code; + fn code(&self) -> Self::Code; +} + +/// Trait for deserialization of Trusty messages. +trait TrustyDeserialize: TrustyMessageId + Sized { + fn from_code_and_data(cmd: u32, data: &[u8]) -> Result; +} + +fn deserialize_trusty_request_message(data: &[u8]) -> Result { + let (raw_cmd, data) = ::deserialize(data)?; + let cmd = raw_cmd >> TRUSTY_CMD_SHIFT; + if (raw_cmd & TRUSTY_RESPONSE_BITMASK) == TRUSTY_RESPONSE_BITMASK { + return Err(Error::UnexpectedResponse); + } + let req = T::from_code_and_data(cmd, data)?; + Ok(req) +} + +/// Deserialize a legacy Trusty request message arriving on the non-secure port. +pub fn deserialize_trusty_req(data: &[u8]) -> Result { + deserialize_trusty_request_message(data) +} + +/// Deserialize a legacy Trusty request message arriving on the secure port. +pub fn deserialize_trusty_secure_req(data: &[u8]) -> Result { + deserialize_trusty_request_message(data) +} + +/// Trait to allow serialization of Trusty messages. +pub trait TrustySerialize: TrustyMessageId { + fn raw_code(&self) -> u32; + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error>; +} + +/// The result of a legacy operation is either a response message or an error code associated with +/// the original command. +pub enum LegacyResult { + Ok(T), + Err { cmd: u32, code: ErrorCode }, +} + +impl LegacyResult { + /// Return the command code associated with the result. + fn cmd(&self) -> u32 { + match self { + LegacyResult::Ok(rsp) => rsp.raw_code(), + LegacyResult::Err { cmd, code: _code } => *cmd, + } + } +} + +/// Serialize a Trusty response message in the form: +/// - command code: 32-bit integer (native endian) +/// - return code: 32-bit integer (native endian) +/// - encoded response data (if return code is 0/Ok). +/// +/// Note that some legacy response messages (e.g. [`GetDeviceInfoResponse`], +/// [`GetAuthTokenKeyResponse`]) do not use this encoding format. +fn serialize_trusty_response_message( + result: LegacyResult, +) -> Result, Error> { + let cmd = result.cmd(); + // None of the supported response messages are large enough to require fragmentation, so always + // mark this as the final response. + let raw_cmd = cmd << TRUSTY_CMD_SHIFT | TRUSTY_RESPONSE_BITMASK | TRUSTY_STOP_BITMASK; + let mut buf = Vec::new(); + buf.try_reserve(LEGACY_NON_SEC_RSP_HEADER_SIZE).map_err(|_e| Error::AllocationFailed)?; + buf.extend_from_slice(&raw_cmd.to_ne_bytes()); + + match result { + LegacyResult::Ok(rsp) => { + buf.extend_from_slice(&(ErrorCode::Ok as u32).to_ne_bytes()); + rsp.serialize_into(&mut buf)?; + } + LegacyResult::Err { cmd: _cmd, code } => { + buf.extend_from_slice(&(code as u32).to_ne_bytes()); + } + } + + Ok(buf) +} + +/// Serialize a legacy Trusty response message for the non-secure port. +pub fn serialize_trusty_rsp(rsp: TrustyPerformOpRsp) -> Result, Error> { + serialize_trusty_response_message(LegacyResult::Ok(rsp)) +} + +/// Serialize raw data as a Trusty response message without length prefix. +fn serialize_trusty_raw_rsp(cmd: u32, raw_data: &[u8]) -> Result, Error> { + let raw_cmd = cmd << TRUSTY_CMD_SHIFT | TRUSTY_RESPONSE_BITMASK | TRUSTY_STOP_BITMASK; + let mut buf = Vec::new(); + buf.try_reserve(CMD_SIZE + raw_data.len()).map_err(|_e| Error::AllocationFailed)?; + buf.extend_from_slice(&raw_cmd.to_ne_bytes()); + buf.extend_from_slice(raw_data); + Ok(buf) +} + +/// Serialize a legacy Trusty response message for the secure port. +pub fn serialize_trusty_secure_rsp(rsp: TrustyPerformSecureOpRsp) -> Result, Error> { + match &rsp { + TrustyPerformSecureOpRsp::GetAuthTokenKey(GetAuthTokenKeyResponse { key_material }) => { + // The `KM_GET_AUTH_TOKEN_KEY` response does not include the error code value. (The + // recipient has to distinguish between OK and error responses by the size of the + // response message: 4+32 for OK, 4+4 for error). + serialize_trusty_raw_rsp(rsp.raw_code(), key_material) + } + TrustyPerformSecureOpRsp::GetDeviceInfo(GetDeviceInfoResponse { device_ids }) => { + // The `KM_GET_DEVICE_INFO` response does not include the error code value. (The + // recipient has to distinguish between OK and error response by attempting to parse + // the response data as a CBOR map, and if this fails assume that the response hold + // an error code instead). + // TODO: update this to include explicit error code information if/when the C++ code + // and library are updated. + serialize_trusty_raw_rsp(rsp.raw_code(), device_ids) + } + TrustyPerformSecureOpRsp::GetUdsCerts(GetUdsCertsResponse { uds_certs: _ }) => { + serialize_trusty_response_message(LegacyResult::Ok(rsp)) + } + TrustyPerformSecureOpRsp::SetAttestationIds(_) => { + serialize_trusty_response_message(LegacyResult::Ok(rsp)) + } + } +} + +/// Deserialize the header of a non-secure channel Trusty response message to know if the operation +/// succeeded. The Result is the error code of the operation, which is roughly equivalent to the +/// legacy keymaster error. Notice that if the keymint operation was successful the return error +/// code will be `ErrorCode::Ok`. +pub fn deserialize_trusty_rsp_error_code(rsp: &[u8]) -> Result { + if rsp.len() < LEGACY_NON_SEC_RSP_HEADER_SIZE { + return Err(Error::DataTruncated); + } + + let (error_code, _) = u32::deserialize(&rsp[CMD_SIZE..LEGACY_NON_SEC_RSP_HEADER_SIZE])?; + ErrorCode::try_from(error_code as i32).map_err(|_e| Error::InvalidEnumValue(error_code)) +} + +/// Serialize a legacy Trusty error response for the non-secure port. +pub fn serialize_trusty_error_rsp( + op: TrustyKeymasterOperation, + rc: ErrorCode, +) -> Result, Error> { + serialize_trusty_response_message(LegacyResult::::Err { + cmd: op as u32, + code: rc, + }) +} + +/// Serialize a legacy Trusty error response for the secure port. +pub fn serialize_trusty_secure_error_rsp( + op: TrustyKeymasterSecureOperation, + rc: ErrorCode, +) -> Result, Error> { + serialize_trusty_response_message(LegacyResult::::Err { + cmd: op as u32, + code: rc, + }) +} + +/// Trait that serializes an inner message to/from the format used by the legacy C++ Keymaster code. +pub trait InnerSerialize: Sized { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error>; + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error>; +} + +impl InnerSerialize for u64 { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + if data.len() < 8 { + return Err(Error::DataTruncated); + } + let int_data: [u8; 8] = data[..8].try_into().map_err(|_e| Error::DataTruncated)?; + Ok((::from_ne_bytes(int_data), &data[8..])) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + buf.try_reserve(8).map_err(|_e| Error::AllocationFailed)?; + buf.extend_from_slice(&self.to_ne_bytes()); + Ok(()) + } +} + +impl InnerSerialize for u32 { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + if data.len() < 4 { + return Err(Error::DataTruncated); + } + let int_data: [u8; 4] = data[..4].try_into().map_err(|_e| Error::DataTruncated)?; + Ok((::from_ne_bytes(int_data), &data[4..])) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + buf.try_reserve(4).map_err(|_e| Error::AllocationFailed)?; + buf.extend_from_slice(&self.to_ne_bytes()); + Ok(()) + } +} + +impl InnerSerialize for u8 { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + if data.is_empty() { + return Err(Error::DataTruncated); + } + Ok((data[0], &data[1..])) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + buf.try_reserve(1).map_err(|_e| Error::AllocationFailed)?; + buf.push(*self); + Ok(()) + } +} + +impl InnerSerialize for bool { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + let (v, rest) = ::deserialize(data)?; + Ok((v != 0, rest)) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + (*self as u32).serialize_into(buf) + } +} + +impl InnerSerialize for Vec { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + let (len, rest) = ::deserialize(data)?; + let len = len as usize; + if rest.len() < len { + return Err(Error::DataTruncated); + } + let mut buf = Vec::new(); + buf.try_reserve(len).map_err(|_e| Error::AllocationFailed)?; + buf.extend_from_slice(&rest[..len]); + Ok((buf, &rest[len..])) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + buf.try_reserve(4 + self.len()).map_err(|_e| Error::AllocationFailed)?; + let len = self.len() as u32; + buf.extend_from_slice(&len.to_ne_bytes()); + buf.extend_from_slice(self); + Ok(()) + } +} + +impl InnerSerialize for KmVersion { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + let (v, rest) = ::deserialize(data)?; + Ok((Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, rest)) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + (*self as u32).serialize_into(buf) + } +} + +impl InnerSerialize for Algorithm { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + let (v, rest) = ::deserialize(data)?; + Ok((Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, rest)) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + (*self as u32).serialize_into(buf) + } +} + +impl InnerSerialize for VerifiedBootState { + fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { + let (v, rest) = ::deserialize(data)?; + Ok((Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, rest)) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + (*self as u32).serialize_into(buf) + } +} + +// Legacy messages of interest from `android_keymaster_messages.h`. + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetVersionRequest {} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetVersionResponse { + pub major_ver: u8, + pub minor_ver: u8, + pub subminor_ver: u8, +} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetVersion2Request { + pub max_message_version: u32, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetVersion2Response { + pub max_message_version: u32, + pub km_version: KmVersion, + pub km_date: u32, +} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct ConfigureBootPatchlevelRequest { + pub boot_patchlevel: u32, // YYYMMDD +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct ConfigureBootPatchlevelResponse {} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct ConfigureVerifiedBootInfoRequest { + pub boot_state: Vec, + pub bootloader_state: Vec, + pub vbmeta_digest: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct ConfigureVerifiedBootInfoResponse {} + +#[derive(Clone, PartialEq, Eq, LegacySerialize, ZeroizeOnDrop)] +pub struct SetAttestationIdsRequest { + pub brand: Vec, + pub device: Vec, + pub product: Vec, + pub serial: Vec, + pub imei: Vec, + pub meid: Vec, + pub manufacturer: Vec, + pub model: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct SetAttestationIdsResponse {} + +#[derive(Clone, PartialEq, Eq, LegacySerialize, ZeroizeOnDrop)] +pub struct SetAttestationIdsKM3Request { + pub base: SetAttestationIdsRequest, + pub second_imei: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct SetAttestationIdsKM3Response {} + +// Legacy messages of interest from `trusty_keymaster_messages.h`. + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetAuthTokenKeyRequest {} +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct GetAuthTokenKeyResponse { + pub key_material: Vec, +} + +/// The serialization of a `GET_AUTH_TOKEN_KEY` response does not include a length field before the +/// contents of the key, so the auto-derive implementation can't be used. (This also means that +/// `deserialize()` can't be implemented, because there is no length information available.) +impl InnerSerialize for GetAuthTokenKeyResponse { + fn deserialize(_data: &[u8]) -> Result<(Self, &[u8]), Error> { + Err(Error::UnexpectedResponse) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + buf.try_reserve(self.key_material.len()).map_err(|_e| Error::AllocationFailed)?; + buf.extend_from_slice(&self.key_material); + Ok(()) + } +} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetDeviceInfoRequest {} +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] +pub struct GetDeviceInfoResponse { + // Device ID information encoded as a CBOR map. + pub device_ids: Vec, +} + +/// The serialization of a `GET_DEVICE_INFO` response does not include a length field before the +/// contents, so the auto-derive implementation can't be used. (This also means that `deserialize()` +/// can't be implemented, because there is no length information available.) +impl InnerSerialize for GetDeviceInfoResponse { + fn deserialize(_data: &[u8]) -> Result<(Self, &[u8]), Error> { + Err(Error::UnexpectedResponse) + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + buf.try_reserve(self.device_ids.len()).map_err(|_e| Error::AllocationFailed)?; + buf.extend_from_slice(&self.device_ids); + Ok(()) + } +} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetUdsCertsRequest {} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct GetUdsCertsResponse { + pub uds_certs: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct SetBootParamsRequest { + pub os_version: u32, + pub os_patchlevel: u32, // YYYYMM + pub device_locked: bool, + pub verified_boot_state: VerifiedBootState, + pub verified_boot_key: Vec, + pub verified_boot_hash: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct SetBootParamsResponse {} + +#[derive(Clone, PartialEq, Eq, LegacySerialize, ZeroizeOnDrop)] +pub struct SetAttestationKeyRequest { + #[zeroize(skip)] + pub algorithm: Algorithm, + pub key_data: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct SetAttestationKeyResponse {} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct AppendAttestationCertChainRequest { + pub algorithm: Algorithm, + pub cert_data: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct AppendAttestationCertChainResponse {} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct ClearAttestationCertChainRequest { + pub algorithm: Algorithm, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct ClearAttestationCertChainResponse {} + +#[derive(Clone, PartialEq, Eq, LegacySerialize, ZeroizeOnDrop)] +pub struct SetWrappedAttestationKeyRequest { + #[zeroize(skip)] + pub algorithm: Algorithm, + pub key_data: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct SetWrappedAttestationKeyResponse {} + +#[derive(Clone, PartialEq, Eq, LegacySerialize, ZeroizeOnDrop)] +pub struct AppendUdsCertificateRequest { + pub cert_data: Vec, +} +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct AppendUdsCertificateResponse {} + +#[derive(Clone, PartialEq, Eq, LegacySerialize, ZeroizeOnDrop)] +pub struct ClearUdsCertificateRequest {} + +#[derive(Clone, PartialEq, Eq, Debug, LegacySerialize)] +pub struct ClearUdsCertificateResponse {} + +macro_rules! declare_req_rsp_enums { + { + $cenum:ident => ($reqenum:ident, $rspenum:ident) + { + $( $cname:ident = $cvalue:expr => ($reqtyp:ty, $rsptyp:ty) , )* + } + } => { + #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, N)] + pub enum $cenum { + $( $cname = $cvalue, )* + } + pub enum $reqenum { + $( $cname($reqtyp), )* + } + pub enum $rspenum { + $( $cname($rsptyp), )* + } + impl TrustyMessageId for $reqenum { + type Code = $cenum; + fn code(&self) -> $cenum { + match self { + $( Self::$cname(_) => $cenum::$cname, )* + } + } + } + impl TrustyDeserialize for $reqenum { + fn from_code_and_data(cmd: u32, data: &[u8]) -> Result { + let (req, rest) = match cmd { + $( + $cvalue => { + let (req, rest) = <$reqtyp>::deserialize(data)?; + ($reqenum::$cname(req), rest) + } + )* + _ => return Err(Error::UnknownCommand(cmd)), + }; + if !rest.is_empty() { + return Err(Error::ExcessData(rest.len())); + } + Ok(req) + } + } + impl TrustyMessageId for $rspenum { + type Code = $cenum; + fn code(&self) -> $cenum { + match self { + $( Self::$cname(_) => $cenum::$cname, )* + } + } + } + impl TrustySerialize for $rspenum { + fn raw_code(&self) -> u32 { + self.code() as u32 + } + fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { + match self { + $( Self::$cname(rsp) => rsp.serialize_into(buf), )* + } + } + } + }; +} + +// Possible legacy Cuttlefish Keymaster operation requests, as: +// - an enum value with an explicit numeric value +// - a request enum which has an operation code associated to each variant +// - a response enum which has the same operation code associated to each variant. +// +// Numerical values for discriminants match the values in +// system/keymaster/include/keymaster/android_keymaster_messages.h +declare_req_rsp_enums! { CuttlefishKeymasterOperation => (CuttlefishPerformOpReq, CuttlefishPerformOpRsp) { + ConfigureBootPatchlevel = 33 => (ConfigureBootPatchlevelRequest, ConfigureBootPatchlevelResponse), + ConfigureVerifiedBootInfo = 34 => (ConfigureVerifiedBootInfoRequest, ConfigureVerifiedBootInfoResponse), + SetAttestationIds = 38 => (SetAttestationIdsRequest, SetAttestationIdsResponse), +} } + +// Possible legacy Trusty Keymaster operation requests for the non-secure port. +// +// Numerical values for discriminants match the values in +// trusty/user/app/keymaster/ipc/keymaster_ipc.h. +declare_req_rsp_enums! { TrustyKeymasterOperation => (TrustyPerformOpReq, TrustyPerformOpRsp) { + GetVersion = 7 => (GetVersionRequest, GetVersionResponse), + GetVersion2 = 28 => (GetVersion2Request, GetVersion2Response), + SetBootParams = 0x1000 => (SetBootParamsRequest, SetBootParamsResponse), + + // Provisioning-related requests. Changes here should be reflected in `is_trusty_provisioning_{code,req}`. + SetAttestationKey = 0x2000 => (SetAttestationKeyRequest, SetAttestationKeyResponse), + AppendAttestationCertChain = 0x3000 => (AppendAttestationCertChainRequest, AppendAttestationCertChainResponse), + ClearAttestationCertChain = 0xa000 => (ClearAttestationCertChainRequest, ClearAttestationCertChainResponse), + SetWrappedAttestationKey = 0xb000 => (SetWrappedAttestationKeyRequest, SetWrappedAttestationKeyResponse), + SetAttestationIds = 0xc000 => (SetAttestationIdsRequest, SetAttestationIdsResponse), + SetAttestationIdsKM3 = 0xc001 => (SetAttestationIdsKM3Request, SetAttestationIdsKM3Response), + ConfigureBootPatchlevel = 0xd0000 => (ConfigureBootPatchlevelRequest, ConfigureBootPatchlevelResponse), + AppendUdsCertificate = 0xe0000 => (AppendUdsCertificateRequest, AppendUdsCertificateResponse), + ClearUdsCertificate = 0xe0001 => (ClearUdsCertificateRequest, ClearUdsCertificateResponse), +} } + +// Possible legacy Trusty Keymaster operation requests for the secure port. +// +// Numerical values for discriminants match the values in +// trusty/user/base/interface/keymaster/include/interface/keymaster/keymaster.h +declare_req_rsp_enums! { TrustyKeymasterSecureOperation => (TrustyPerformSecureOpReq, TrustyPerformSecureOpRsp) { + GetAuthTokenKey = 0 => (GetAuthTokenKeyRequest, GetAuthTokenKeyResponse), + GetDeviceInfo = 1 => (GetDeviceInfoRequest, GetDeviceInfoResponse), + GetUdsCerts = 2 => (GetUdsCertsRequest, GetUdsCertsResponse), + SetAttestationIds = 0xc000 => (SetAttestationIdsRequest, SetAttestationIdsResponse), +} } + +/// Indicate whether a request message is a bootloader message. +pub fn is_trusty_bootloader_code(code: u32) -> bool { + matches!( + TrustyKeymasterOperation::n(code), + Some(TrustyKeymasterOperation::SetBootParams) + | Some(TrustyKeymasterOperation::ConfigureBootPatchlevel) + ) +} + +/// Indicate whether a request message is a bootloader message. +pub fn is_trusty_bootloader_req(req: &TrustyPerformOpReq) -> bool { + matches!( + req, + TrustyPerformOpReq::SetBootParams(_) | TrustyPerformOpReq::ConfigureBootPatchlevel(_) + ) +} + +/// Indicate whether a request message is a provisioning message. +pub fn is_trusty_provisioning_code(code: u32) -> bool { + matches!( + TrustyKeymasterOperation::n(code), + Some(TrustyKeymasterOperation::SetAttestationKey) + | Some(TrustyKeymasterOperation::AppendAttestationCertChain) + | Some(TrustyKeymasterOperation::ClearAttestationCertChain) + | Some(TrustyKeymasterOperation::SetWrappedAttestationKey) + | Some(TrustyKeymasterOperation::SetAttestationIds) + | Some(TrustyKeymasterOperation::SetAttestationIdsKM3) + | Some(TrustyKeymasterOperation::AppendUdsCertificate) + | Some(TrustyKeymasterOperation::ClearUdsCertificate) + ) +} + +/// Indicate whether a request message is a provisioning message. +pub fn is_trusty_provisioning_req(req: &TrustyPerformOpReq) -> bool { + matches!( + req, + TrustyPerformOpReq::SetAttestationKey(_) + | TrustyPerformOpReq::AppendAttestationCertChain(_) + | TrustyPerformOpReq::ClearAttestationCertChain(_) + | TrustyPerformOpReq::SetWrappedAttestationKey(_) + | TrustyPerformOpReq::SetAttestationIds(_) + | TrustyPerformOpReq::SetAttestationIdsKM3(_) + | TrustyPerformOpReq::AppendUdsCertificate(_) + | TrustyPerformOpReq::ClearUdsCertificate(_) + ) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_inner_serialize() { + let msg = SetBootParamsRequest { + // `u32` encoding uses native byte order so use symmetric values + os_version: 0x01010101, + os_patchlevel: 0x02020202, + device_locked: false, + verified_boot_state: VerifiedBootState::Unverified, + verified_boot_key: vec![1, 2, 3], + verified_boot_hash: vec![5, 4, 3], + }; + #[cfg(target_endian = "little")] + let hex_data = concat!( + "01010101", // os_version + "02020202", // os_patchlevel + "00000000", // device_locked + "02000000", // verified_boot_state + "03000000", "010203", // verified_boot_key + "03000000", "050403", // verified_boot_key + ); + #[cfg(target_endian = "big")] + let hex_data = concat!( + "01010101", // os_version + "02020202", // os_patchlevel + "00000000", // device_locked + "02000002", // verified_boot_state + "00000003", "010203", // verified_boot_key + "00000003", "050403", // verified_boot_key + ); + let data = hex::decode(hex_data).unwrap(); + + let mut got_data = Vec::new(); + msg.serialize_into(&mut got_data).unwrap(); + assert_eq!(hex::encode(got_data), hex_data); + + let (got, rest) = SetBootParamsRequest::deserialize(&data).unwrap(); + assert!(rest.is_empty()); + assert!(got == msg); + } + #[test] + fn test_get_version_serialize() { + let msg = GetVersionResponse { major_ver: 1, minor_ver: 2, subminor_ver: 3 }; + let data = vec![1, 2, 3]; + + let mut got_data = Vec::new(); + msg.serialize_into(&mut got_data).unwrap(); + assert_eq!(got_data, data); + + let (got, rest) = GetVersionResponse::deserialize(&data).unwrap(); + assert!(rest.is_empty()); + assert!(got == msg); + } + #[test] + fn test_inner_deserialize_fail() { + let data = "010101"; // too short + let data = hex::decode(data).unwrap(); + let result = ConfigureBootPatchlevelRequest::deserialize(&data); + assert!(result.is_err()); + } + #[test] + fn test_trusty_serialize_rsp() { + let msg = TrustyPerformSecureOpRsp::GetAuthTokenKey(GetAuthTokenKeyResponse { + key_material: vec![1, 2, 3], + }); + #[cfg(target_endian = "little")] + let data = concat!("03000000", "010203"); + #[cfg(target_endian = "big")] + let data = concat!("00000003", "010203"); + + let got_data = serialize_trusty_secure_rsp(msg).unwrap(); + assert_eq!(hex::encode(got_data), data); + } + #[test] + fn test_get_uds_certs_rsp_serialize() { + let msg = + TrustyPerformSecureOpRsp::GetUdsCerts(GetUdsCertsResponse { uds_certs: vec![1, 2, 3] }); + #[cfg(target_endian = "little")] + let data = concat!( + /* cmd */ "0b000000", /* rc */ "00000000", /* len */ "03000000", + /* data */ "010203" + ); + #[cfg(target_endian = "big")] + let data = concat!( + /* cmd */ "0000000b", /* rc */ "00000000", /* len */ "00000003", + /* data */ "010203" + ); + let got_data = serialize_trusty_secure_rsp(msg).unwrap(); + assert_eq!(hex::encode(got_data), data); + } +} diff --git a/libs/rust/wire/src/lib.rs b/libs/rust/wire/src/lib.rs new file mode 100644 index 0000000..90d3699 --- /dev/null +++ b/libs/rust/wire/src/lib.rs @@ -0,0 +1,581 @@ +// Copyright 2022, 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. + +//! Types and macros for communication between HAL and TA + +// Allow missing docs in this crate as the types here are generally 1:1 with the HAL +// interface definitions. + +/// Re-export of crate used for CBOR encoding. +pub use ciborium as cbor; +/// Re-export of crate used for COSE encoding. +pub use coset; + +pub mod keymint; +pub mod legacy; +pub mod rpc; +pub mod secureclock; +pub mod sharedsecret; +pub mod types; +use coset::TaggedCborSerializable; +pub use types::*; + +#[cfg(test)] +mod tests; + +/// Macro that emits an implementation of `TryFrom` for an enum type that has `[derive(N)]` +/// attached to it. The implementation assumes that `ValueNotRecognized` has a variant with the +/// same name as the enum. +#[macro_export] +macro_rules! try_from_n { + { $ename:ident } => { + impl core::convert::TryFrom for $ename { + type Error = $crate::ValueNotRecognized; + fn try_from(value: i32) -> Result { + Self::n(value).ok_or($crate::ValueNotRecognized::$ename) + } + } + }; +} + +/// Function that mimics `vec![; ]` but which detects allocation failure with the given +/// error. +pub fn vec_try_fill_with_alloc_err( + elem: T, + len: usize, + alloc_err: fn() -> E, +) -> Result, E> { + let mut v = Vec::new(); + v.try_reserve(len).map_err(|_e| alloc_err())?; + v.resize(len, elem); + Ok(v) +} + +/// Function that mimics `vec![x1, x2, x3, x4]` but which detects allocation failure with the given +/// error. +pub fn vec_try4_with_alloc_err( + x1: T, + x2: T, + x3: T, + x4: T, + alloc_err: fn() -> E, +) -> Result, E> { + let mut v = Vec::new(); + match v.try_reserve(4) { + Err(_e) => Err(alloc_err()), + Ok(_) => { + v.push(x1); + v.push(x2); + v.push(x3); + v.push(x4); + Ok(v) + } + } +} + +/// Function that mimics `vec![x1, x2, x3]` but which detects allocation failure with the given +/// error. +pub fn vec_try3_with_alloc_err( + x1: T, + x2: T, + x3: T, + alloc_err: fn() -> E, +) -> Result, E> { + let mut v = Vec::new(); + match v.try_reserve(3) { + Err(_e) => Err(alloc_err()), + Ok(_) => { + v.push(x1); + v.push(x2); + v.push(x3); + Ok(v) + } + } +} + +/// Function that mimics `vec![x1, x2]` but which detects allocation failure with the given error. +pub fn vec_try2_with_alloc_err( + x1: T, + x2: T, + alloc_err: fn() -> E, +) -> Result, E> { + let mut v = Vec::new(); + match v.try_reserve(2) { + Err(_e) => Err(alloc_err()), + Ok(_) => { + v.push(x1); + v.push(x2); + Ok(v) + } + } +} + +/// Function that mimics `vec![x1]` but which detects allocation failure with the given error. +pub fn vec_try1_with_alloc_err(x1: T, alloc_err: fn() -> E) -> Result, E> { + let mut v = Vec::new(); + match v.try_reserve(1) { + Err(_e) => Err(alloc_err()), + Ok(_) => { + v.push(x1); + Ok(v) + } + } +} + +/// Macro that mimics `vec!` but which detects allocation failure. +#[macro_export] +macro_rules! vec_try { + { $elem:expr ; $len:expr } => { + $crate::vec_try_fill_with_alloc_err($elem, $len, || $crate::CborError::AllocationFailed) + }; + { $x1:expr, $x2:expr, $x3:expr, $x4:expr $(,)? } => { + $crate::vec_try4_with_alloc_err($x1, $x2, $x3, $x4, || $crate::CborError::AllocationFailed) + }; + { $x1:expr, $x2:expr, $x3:expr $(,)? } => { + $crate::vec_try3_with_alloc_err($x1, $x2, $x3, || $crate::CborError::AllocationFailed) + }; + { $x1:expr, $x2:expr $(,)? } => { + $crate::vec_try2_with_alloc_err($x1, $x2, || $crate::CborError::AllocationFailed) + }; + { $x1:expr $(,)? } => { + $crate::vec_try1_with_alloc_err($x1, || $crate::CborError::AllocationFailed) + }; +} + +/// Marker structure indicating that the EOF was encountered when reading CBOR data. +#[derive(Debug)] +pub struct EndOfFile; + +/// Error type for failures in encoding or decoding CBOR types. +pub enum CborError { + /// CBOR decoding failure. + DecodeFailed(cbor::de::Error), + /// CBOR encoding failure. + EncodeFailed, + /// CBOR input had extra data. + ExtraneousData, + /// Integer value outside expected range. + OutOfRangeIntegerValue, + /// Integer value that doesn't match expected set of allowed enum values. + NonEnumValue, + /// Unexpected CBOR item encountered (got, want). + UnexpectedItem(&'static str, &'static str), + /// Value conversion failure. + InvalidValue, + /// Allocation failure. + AllocationFailed, +} + +// Can only implement `Into` due to orphan trait rule. +#[allow(clippy::from_over_into)] +impl Into for CborError { + fn into(self) -> coset::CoseError { + match self { + CborError::DecodeFailed(inner) => coset::CoseError::DecodeFailed(match inner { + cbor::de::Error::Io(_io) => cbor::de::Error::Io(coset::EndOfFile), + cbor::de::Error::Syntax(v) => cbor::de::Error::Syntax(v), + cbor::de::Error::Semantic(sz, msg) => cbor::de::Error::Semantic(sz, msg), + cbor::de::Error::RecursionLimitExceeded => cbor::de::Error::RecursionLimitExceeded, + }), + CborError::EncodeFailed => coset::CoseError::EncodeFailed, + CborError::ExtraneousData => coset::CoseError::ExtraneousData, + CborError::OutOfRangeIntegerValue => coset::CoseError::OutOfRangeIntegerValue, + CborError::NonEnumValue => coset::CoseError::OutOfRangeIntegerValue, + CborError::UnexpectedItem(got, want) => coset::CoseError::UnexpectedItem(got, want), + CborError::InvalidValue => coset::CoseError::EncodeFailed, + CborError::AllocationFailed => coset::CoseError::EncodeFailed, + } + } +} + +impl From> for CborError { + fn from(e: cbor::de::Error) -> Self { + // Make sure we use our [`EndOfFile`] marker. + use cbor::de::Error::{Io, RecursionLimitExceeded, Semantic, Syntax}; + let e = match e { + Io(_) => Io(EndOfFile), + Syntax(x) => Syntax(x), + Semantic(a, b) => Semantic(a, b), + RecursionLimitExceeded => RecursionLimitExceeded, + }; + CborError::DecodeFailed(e) + } +} + +impl From> for CborError { + fn from(_e: cbor::ser::Error) -> Self { + CborError::EncodeFailed + } +} + +impl From for CborError { + fn from(_e: cbor::value::Error) -> Self { + CborError::InvalidValue + } +} + +impl From for CborError { + fn from(_: core::num::TryFromIntError) -> Self { + CborError::OutOfRangeIntegerValue + } +} + +impl From for CborError { + fn from(e: coset::CoseError) -> Self { + match e { + coset::CoseError::DecodeFailed(inner) => CborError::DecodeFailed(match inner { + cbor::de::Error::Io(_io) => cbor::de::Error::Io(EndOfFile), + cbor::de::Error::Syntax(v) => cbor::de::Error::Syntax(v), + cbor::de::Error::Semantic(sz, msg) => cbor::de::Error::Semantic(sz, msg), + cbor::de::Error::RecursionLimitExceeded => cbor::de::Error::RecursionLimitExceeded, + }), + coset::CoseError::EncodeFailed => CborError::EncodeFailed, + coset::CoseError::ExtraneousData => CborError::ExtraneousData, + coset::CoseError::OutOfRangeIntegerValue => CborError::OutOfRangeIntegerValue, + coset::CoseError::UnregisteredIanaValue => CborError::NonEnumValue, + coset::CoseError::UnregisteredIanaNonPrivateValue => CborError::NonEnumValue, + coset::CoseError::UnexpectedItem(got, want) => CborError::UnexpectedItem(got, want), + coset::CoseError::DuplicateMapKey => { + CborError::UnexpectedItem("dup map key", "unique keys") + } + } + } +} + +impl core::fmt::Debug for CborError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + CborError::DecodeFailed(de) => write!(f, "decode CBOR failure: {:?}", de), + CborError::EncodeFailed => write!(f, "encode CBOR failure"), + CborError::ExtraneousData => write!(f, "extraneous data in CBOR input"), + CborError::OutOfRangeIntegerValue => write!(f, "out of range integer value"), + CborError::NonEnumValue => write!(f, "integer not a valid enum value"), + CborError::UnexpectedItem(got, want) => write!(f, "got {}, expected {}", got, want), + CborError::InvalidValue => write!(f, "invalid CBOR value"), + CborError::AllocationFailed => write!(f, "allocation failed"), + } + } +} + +/// Return an error indicating that an unexpected CBOR type was encountered. +pub fn cbor_type_error(value: &cbor::value::Value, want: &'static str) -> Result { + use cbor::value::Value; + let got = match value { + Value::Integer(_) => "int", + Value::Bytes(_) => "bstr", + Value::Text(_) => "tstr", + Value::Array(_) => "array", + Value::Map(_) => "map", + Value::Tag(_, _) => "tag", + Value::Float(_) => "float", + Value::Bool(_) => "bool", + Value::Null => "null", + _ => "unknown", + }; + Err(CborError::UnexpectedItem(got, want)) +} + +/// Read a [`cbor::value::Value`] from a byte slice, failing if any extra data remains after the +/// `Value` has been read. +pub fn read_to_value(mut slice: &[u8]) -> Result { + let value = cbor::de::from_reader_with_recursion_limit(&mut slice, 16)?; + if slice.is_empty() { + Ok(value) + } else { + Err(CborError::ExtraneousData) + } +} + +/// Trait for types that can be converted to/from a [`cbor::value::Value`]. +pub trait AsCborValue: Sized { + /// Convert a [`cbor::value::Value`] into an instance of the type. + fn from_cbor_value(value: cbor::value::Value) -> Result; + + /// Convert the object into a [`cbor::value::Value`], consuming it along the way. + fn to_cbor_value(self) -> Result; + + /// Create an object instance from serialized CBOR data in a slice. + fn from_slice(slice: &[u8]) -> Result { + Self::from_cbor_value(read_to_value(slice)?) + } + + /// Serialize this object to a vector, consuming it along the way. + fn into_vec(self) -> Result, CborError> { + let mut data = Vec::new(); + cbor::ser::into_writer(&self.to_cbor_value()?, &mut data)?; + Ok(data) + } + + /// Return the name used for this type in a CDDL schema, or `None` if this type does not have a + /// simple CDDL name. (For example, type `Vec` maps to a schema `(+ int)` but doesn't + /// have a name.) + fn cddl_typename() -> Option { + None + } + + /// Return the CDDL schema for this type, or None if this type is primitive (e.g. `int`, `bool`, + /// `bstr`). + fn cddl_schema() -> Option { + None + } + + /// Return a way to refer to this type in CDDL; prefer the CDDL type name if available, + /// use the explicit schema if not. + fn cddl_ref() -> String { + if let Some(item_name) = Self::cddl_typename() { + item_name + } else if let Some(item_schema) = Self::cddl_schema() { + item_schema + } else { + panic!("type with unknown CDDL") + } + } +} + +// Implement the local `AsCborValue` trait for `coset::CoseEncrypt0` ensuring/requiring +// use of the relevant CBOR tag. +impl AsCborValue for coset::CoseEncrypt0 { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Tag(tag, inner_value) if tag == coset::CoseEncrypt0::TAG => { + ::from_cbor_value(*inner_value) + .map_err(|e| e.into()) + } + cbor::value::Value::Tag(_, _) => Err(CborError::UnexpectedItem("tag", "tag 16")), + _ => cbor_type_error(&value, "tag 16"), + } + } + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Tag( + coset::CoseEncrypt0::TAG, + Box::new(coset::AsCborValue::to_cbor_value(self)?), + )) + } + fn cddl_schema() -> Option { + Some(format!("#6.{}(Cose_Encrypt0)", coset::CoseEncrypt0::TAG)) + } +} + +/// An `Option` encodes as `( ? t )`, where `t` is whatever `T` encodes as in CDDL. +impl AsCborValue for Option { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let mut arr = match value { + cbor::value::Value::Array(a) => a, + _ => return Err(CborError::UnexpectedItem("non-arr", "arr")), + }; + match arr.len() { + 0 => Ok(None), + 1 => Ok(Some(::from_cbor_value(arr.remove(0))?)), + _ => Err(CborError::UnexpectedItem("arr len >1", "arr len 0/1")), + } + } + + fn to_cbor_value(self) -> Result { + match self { + Some(t) => Ok(cbor::value::Value::Array(vec_try![t.to_cbor_value()?]?)), + None => Ok(cbor::value::Value::Array(Vec::new())), + } + } + + fn cddl_schema() -> Option { + Some(format!("[? {}]", ::cddl_ref())) + } +} + +/// A `Vec` encodes as `( * t )`, where `t` is whatever `T` encodes as in CDDL. +impl AsCborValue for Vec { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let arr = match value { + cbor::value::Value::Array(a) => a, + _ => return cbor_type_error(&value, "arr"), + }; + let results: Result, _> = arr.into_iter().map(::from_cbor_value).collect(); + results + } + + fn to_cbor_value(self) -> Result { + let values: Result, _> = self.into_iter().map(|v| v.to_cbor_value()).collect(); + Ok(cbor::value::Value::Array(values?)) + } + + fn cddl_schema() -> Option { + Some(format!("[* {}]", ::cddl_ref())) + } +} + +impl AsCborValue for Vec { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Bytes(bstr) => Ok(bstr), + _ => cbor_type_error(&value, "bstr"), + } + } + + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Bytes(self)) + } + + fn cddl_typename() -> Option { + Some("bstr".to_string()) + } +} + +impl AsCborValue for [u8; N] { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let data = match value { + cbor::value::Value::Bytes(bstr) => bstr, + _ => return cbor_type_error(&value, "bstr"), + }; + data.try_into() + .map_err(|_e| CborError::UnexpectedItem("bstr other size", "bstr specific size")) + } + + fn to_cbor_value(self) -> Result { + let mut v = Vec::new(); + if v.try_reserve(self.len()).is_err() { + return Err(CborError::AllocationFailed); + } + v.extend_from_slice(&self); + Ok(cbor::value::Value::Bytes(v)) + } + + fn cddl_typename() -> Option { + Some(format!("bstr .size {}", N)) + } +} + +impl AsCborValue for String { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Text(s) => Ok(s), + _ => cbor_type_error(&value, "tstr"), + } + } + + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Text(self)) + } + + fn cddl_typename() -> Option { + Some("tstr".to_string()) + } +} + +impl AsCborValue for u64 { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Integer(i) => { + i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) + } + v => crate::cbor_type_error(&v, "u64"), + } + } + + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Integer(self.into())) + } + + fn cddl_typename() -> Option { + Some("int".to_string()) + } +} + +impl AsCborValue for i64 { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Integer(i) => { + i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) + } + v => crate::cbor_type_error(&v, "i64"), + } + } + + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Integer(self.into())) + } + + fn cddl_typename() -> Option { + Some("int".to_string()) + } +} + +impl AsCborValue for u32 { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Integer(i) => { + i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) + } + v => crate::cbor_type_error(&v, "u32"), + } + } + + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Integer(self.into())) + } + + fn cddl_typename() -> Option { + Some("int".to_string()) + } +} + +impl AsCborValue for bool { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Bool(b) => Ok(b), + v => crate::cbor_type_error(&v, "bool"), + } + } + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Bool(self)) + } + + fn cddl_typename() -> Option { + Some("bool".to_string()) + } +} + +impl AsCborValue for () { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Null => Ok(()), + v => crate::cbor_type_error(&v, "null"), + } + } + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Null) + } + + fn cddl_typename() -> Option { + Some("null".to_string()) + } +} + +impl AsCborValue for i32 { + fn from_cbor_value(value: cbor::value::Value) -> Result { + match value { + cbor::value::Value::Integer(i) => { + i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) + } + v => crate::cbor_type_error(&v, "i64"), + } + } + + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Integer(self.into())) + } + + fn cddl_typename() -> Option { + Some("int".to_string()) + } +} diff --git a/libs/rust/wire/src/rpc.rs b/libs/rust/wire/src/rpc.rs new file mode 100644 index 0000000..1b529e6 --- /dev/null +++ b/libs/rust/wire/src/rpc.rs @@ -0,0 +1,80 @@ +// Copyright 2022, 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. + +//! Local types that are equivalent to those generated for the IRemotelyProvisionedComponent HAL +//! interface + +use crate::{cbor_type_error, try_from_n, AsCborValue, CborError}; +use enumn::N; +use kmr_derive::AsCborValue; + +/// IRPC HAL Versions +pub const IRPC_V2: i32 = 2; +pub const IRPC_V3: i32 = 3; +/// `AuthenticatedRequest` CDDL schema version +pub const AUTH_REQ_SCHEMA_V1: i32 = 1; +/// `CertificateType` for keymint +pub const CERT_TYPE_KEYMINT: &str = "keymint"; + +/// Indication of whether RKP is operating in test mode. (Only relevant for RKP v1 and v2.) +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct TestMode(pub bool); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[repr(i32)] +pub enum ErrorCode { + Ok = 0, // not in HAL, assumed + Failed = 1, + InvalidMac = 2, + ProductionKeyInTestRequest = 3, + TestKeyInProductionRequest = 4, + InvalidEek = 5, + Removed = 6, +} + +/// The default value for the minimum number of keys supported in a CSR. +pub const MINIMUM_SUPPORTED_KEYS_IN_CSR: i32 = 20; + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct HardwareInfo { + pub version_number: i32, + pub rpc_author_name: String, + pub supported_eek_curve: EekCurve, + pub unique_id: Option, + pub supported_num_keys_in_csr: i32, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue, N)] +#[repr(i32)] +pub enum EekCurve { + None = 0, + P256 = 1, + Curve25519 = 2, +} +try_from_n!(EekCurve); + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct MacedPublicKey { + pub maced_key: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct ProtectedData { + pub protected_data: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, AsCborValue)] +pub struct DeviceInfo { + pub device_info: Vec, +} diff --git a/libs/rust/wire/src/secureclock.rs b/libs/rust/wire/src/secureclock.rs new file mode 100644 index 0000000..89c9230 --- /dev/null +++ b/libs/rust/wire/src/secureclock.rs @@ -0,0 +1,32 @@ +// Copyright 2022, 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. + +//! Local types that are equivalent to those generated for the SecureClock HAL interface + +use crate::{cbor_type_error, AsCborValue, CborError}; +use kmr_derive::AsCborValue; + +pub const TIME_STAMP_MAC_LABEL: &[u8] = b"Auth Verification"; + +#[derive(Debug, Clone, Eq, Hash, PartialEq, AsCborValue)] +pub struct TimeStampToken { + pub challenge: i64, + pub timestamp: Timestamp, + pub mac: Vec, +} + +#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, AsCborValue)] +pub struct Timestamp { + pub milliseconds: i64, +} diff --git a/libs/rust/wire/src/sharedsecret.rs b/libs/rust/wire/src/sharedsecret.rs new file mode 100644 index 0000000..ed0a72b --- /dev/null +++ b/libs/rust/wire/src/sharedsecret.rs @@ -0,0 +1,27 @@ +// Copyright 2022, 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. + +//! Local types that are equivalent to those generated for the SharedSecret HAL interface + +use crate::{cbor_type_error, AsCborValue, CborError}; +use kmr_derive::AsCborValue; + +pub const KEY_AGREEMENT_LABEL: &str = "KeymasterSharedMac"; +pub const KEY_CHECK_LABEL: &str = "Keymaster HMAC Verification"; + +#[derive(Debug, Clone, Eq, Hash, PartialEq, Default, AsCborValue)] +pub struct SharedSecretParameters { + pub seed: Vec, + pub nonce: Vec, +} diff --git a/libs/rust/wire/src/tests.rs b/libs/rust/wire/src/tests.rs new file mode 100644 index 0000000..3942a6a --- /dev/null +++ b/libs/rust/wire/src/tests.rs @@ -0,0 +1,44 @@ +// Copyright 2022, 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. + +use super::*; +use crate::cbor::value::Value; + +#[test] +fn test_read_to_value_ok() { + let tests = vec![ + ("01", Value::Integer(1.into())), + ("40", Value::Bytes(vec![])), + ("60", Value::Text(String::new())), + ]; + for (hexdata, want) in tests { + let data = hex::decode(hexdata).unwrap(); + let got = read_to_value(&data).unwrap(); + assert_eq!(got, want, "failed for {}", hexdata); + } +} + +#[test] +fn test_read_to_value_fail() { + let tests = vec![ + ("0101", CborError::ExtraneousData), + ("43", CborError::DecodeFailed(cbor::de::Error::Io(EndOfFile))), + ("8001", CborError::ExtraneousData), + ]; + for (hexdata, want_err) in tests { + let data = hex::decode(hexdata).unwrap(); + let got_err = read_to_value(&data).expect_err("decoding expected to fail"); + assert_eq!(format!("{:?}", got_err), format!("{:?}", want_err), "failed for {}", hexdata); + } +} diff --git a/libs/rust/wire/src/types.rs b/libs/rust/wire/src/types.rs new file mode 100644 index 0000000..d78b745 --- /dev/null +++ b/libs/rust/wire/src/types.rs @@ -0,0 +1,667 @@ +// Copyright 2022, 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. + +use crate::keymint::{ + AttestationKey, HardwareAuthToken, KeyCharacteristics, KeyCreationResult, KeyFormat, + KeyMintHardwareInfo, KeyParam, KeyPurpose, +}; +use crate::rpc; +use crate::secureclock::TimeStampToken; +use crate::sharedsecret::SharedSecretParameters; +use crate::{cbor, cbor_type_error, vec_try, AsCborValue, CborError}; +use enumn::N; +use kmr_derive::AsCborValue; + +/// Key size in bits. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue)] +pub struct KeySizeInBits(pub u32); + +/// RSA exponent. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, AsCborValue)] +pub struct RsaExponent(pub u64); + +/// Default maximum supported size for CBOR-serialized messages. +pub const DEFAULT_MAX_SIZE: usize = 4096; + +/// Marker type indicating failure to convert into a wire type. For `enum` wire types, the variant +/// names match the `enum` whose value failed to convert. +#[derive(Debug)] +pub enum ValueNotRecognized { + // Enum type names. + KeyPurpose, + Algorithm, + BlockMode, + Digest, + PaddingMode, + EcCurve, + ErrorCode, + HardwareAuthenticatorType, + KeyFormat, + KeyOrigin, + SecurityLevel, + Tag, + TagType, + KmVersion, + EekCurve, + Origin, + // Non-enum types. + Bool, + Blob, + DateTime, + Integer, + LongInteger, +} + +/// Trait that associates an enum value of the specified type with a type. +/// Values of the `enum` type `T` are used to identify particular message types. +/// A message type implements `Code` to indicate which `enum` value it is +/// associated with. +/// +/// For example, an `enum WhichMsg { Hello, Goodbye }` could be used to distinguish +/// between `struct HelloMsg` and `struct GoodbyeMsg` instances, in which case the +/// latter types would both implement `Code` with `CODE` values of +/// `WhichMsg::Hello` and `WhichMsg::Goodbye` respectively. +pub trait Code { + /// The enum value identifying this request/response. + const CODE: T; + /// Return the enum value associated with the underlying type of this item. + fn code(&self) -> T { + Self::CODE + } +} + +/// Internal equivalent of the `keymint::BeginResult` type; instead of the Binder object reference +/// there is an opaque `op_handle` value that the bottom half implementation uses to identify the +/// in-progress operation. This field is included as an extra parameter in all of the per-operation +/// ...Request types. +#[derive(Debug, Default, AsCborValue)] +pub struct InternalBeginResult { + pub challenge: i64, + pub params: Vec, + // Extra for internal use: returned by bottom half of KeyMint implementation, used on + // all subsequent operation methods to identify the operation. + pub op_handle: i64, +} + +// The following types encapsulate the arguments to each method into a corresponding ..Request +// struct, and the return value and out parameters into a corresponding ..Response struct. +// These are currently hand-generated, but they could be auto-generated from the AIDL spec. + +// IKeyMintDevice methods. +#[derive(Debug, AsCborValue)] +pub struct GetHardwareInfoRequest {} +#[derive(Debug, AsCborValue)] +pub struct GetHardwareInfoResponse { + pub ret: KeyMintHardwareInfo, +} +#[derive(Debug, AsCborValue)] +pub struct AddRngEntropyRequest { + pub data: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct AddRngEntropyResponse {} +#[derive(Debug, AsCborValue)] +pub struct GenerateKeyRequest { + pub key_params: Vec, + pub attestation_key: Option, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateKeyResponse { + pub ret: KeyCreationResult, +} +#[derive(Debug, AsCborValue)] +pub struct ImportKeyRequest { + pub key_params: Vec, + pub key_format: KeyFormat, + pub key_data: Vec, + pub attestation_key: Option, +} +#[derive(Debug, AsCborValue)] +pub struct ImportKeyResponse { + pub ret: KeyCreationResult, +} +#[derive(Debug, AsCborValue)] +pub struct ImportWrappedKeyRequest { + pub wrapped_key_data: Vec, + pub wrapping_key_blob: Vec, + pub masking_key: Vec, + pub unwrapping_params: Vec, + pub password_sid: i64, + pub biometric_sid: i64, +} +#[derive(Debug, AsCborValue)] +pub struct ImportWrappedKeyResponse { + pub ret: KeyCreationResult, +} +#[derive(Debug, AsCborValue)] +pub struct UpgradeKeyRequest { + pub key_blob_to_upgrade: Vec, + pub upgrade_params: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct UpgradeKeyResponse { + pub ret: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct DeleteKeyRequest { + pub key_blob: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct DeleteKeyResponse {} +#[derive(Debug, AsCborValue)] +pub struct DeleteAllKeysRequest {} +#[derive(Debug, AsCborValue)] +pub struct DeleteAllKeysResponse {} +#[derive(Debug, AsCborValue)] +pub struct DestroyAttestationIdsRequest {} +#[derive(Debug, AsCborValue)] +pub struct DestroyAttestationIdsResponse {} +#[derive(Debug, AsCborValue)] +pub struct BeginRequest { + pub purpose: KeyPurpose, + pub key_blob: Vec, + pub params: Vec, + pub auth_token: Option, +} +#[derive(Debug, AsCborValue)] +pub struct BeginResponse { + pub ret: InternalBeginResult, // special case: no Binder ref here +} +#[derive(Debug, AsCborValue)] +pub struct EarlyBootEndedRequest {} +#[derive(Debug, AsCborValue)] +pub struct EarlyBootEndedResponse {} +#[derive(Debug, AsCborValue)] +pub struct ConvertStorageKeyToEphemeralRequest { + pub storage_key_blob: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct ConvertStorageKeyToEphemeralResponse { + pub ret: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct GetKeyCharacteristicsRequest { + pub key_blob: Vec, + pub app_id: Vec, + pub app_data: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct GetKeyCharacteristicsResponse { + pub ret: Vec, +} + +#[derive(Debug, AsCborValue)] +pub struct GetRootOfTrustChallengeRequest {} + +#[derive(Debug, AsCborValue)] +pub struct GetRootOfTrustChallengeResponse { + pub ret: [u8; 16], +} + +#[derive(Debug, AsCborValue)] +pub struct GetRootOfTrustRequest { + pub challenge: [u8; 16], +} +#[derive(Debug, AsCborValue)] +pub struct GetRootOfTrustResponse { + pub ret: Vec, +} + +#[derive(Debug, AsCborValue)] +pub struct SendRootOfTrustRequest { + pub root_of_trust: Vec, +} + +#[derive(Debug, AsCborValue)] +pub struct SendRootOfTrustResponse {} + +#[derive(Debug, AsCborValue)] +pub struct SetAdditionalAttestationInfoRequest { + pub info: Vec, +} + +#[derive(Debug, AsCborValue)] +pub struct SetAdditionalAttestationInfoResponse {} + +// IKeyMintOperation methods. These ...Request structures include an extra `op_handle` field whose +// value was returned in the `InternalBeginResult` type and which identifies the operation in +// progress. +// +// `Debug` deliberately not derived to reduce the chances of inadvertent leakage of private info. +#[derive(Debug, Clone, AsCborValue)] +pub struct UpdateAadRequest { + pub op_handle: i64, // Extra for internal use, from `InternalBeginResult`. + pub input: Vec, + pub auth_token: Option, + pub timestamp_token: Option, +} +#[derive(Debug, AsCborValue)] +pub struct UpdateAadResponse {} +#[derive(Debug, Clone, AsCborValue)] +pub struct UpdateRequest { + pub op_handle: i64, // Extra for internal use, from `InternalBeginResult`. + pub input: Vec, + pub auth_token: Option, + pub timestamp_token: Option, +} +#[derive(Debug, AsCborValue)] +pub struct UpdateResponse { + pub ret: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct FinishRequest { + pub op_handle: i64, // Extra for internal use, from `InternalBeginResult`. + pub input: Option>, + pub signature: Option>, + pub auth_token: Option, + pub timestamp_token: Option, + pub confirmation_token: Option>, +} +#[derive(Debug, AsCborValue)] +pub struct FinishResponse { + pub ret: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct AbortRequest { + pub op_handle: i64, // Extra for internal use, from `InternalBeginResult`. +} +#[derive(Debug, AsCborValue)] +pub struct AbortResponse {} + +// IRemotelyProvisionedComponent methods. + +#[derive(Debug, AsCborValue)] +pub struct GetRpcHardwareInfoRequest {} +#[derive(Debug, AsCborValue)] +pub struct GetRpcHardwareInfoResponse { + pub ret: rpc::HardwareInfo, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateEcdsaP256KeyPairRequest { + pub test_mode: bool, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateEcdsaP256KeyPairResponse { + pub maced_public_key: rpc::MacedPublicKey, + pub ret: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateCertificateRequestRequest { + pub test_mode: bool, + pub keys_to_sign: Vec, + pub endpoint_encryption_cert_chain: Vec, + pub challenge: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateCertificateRequestResponse { + pub device_info: rpc::DeviceInfo, + pub protected_data: rpc::ProtectedData, + pub ret: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateCertificateRequestV2Request { + pub keys_to_sign: Vec, + pub challenge: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateCertificateRequestV2Response { + pub ret: Vec, +} + +// ISharedSecret methods. +#[derive(Debug, AsCborValue)] +pub struct GetSharedSecretParametersRequest {} +#[derive(Debug, AsCborValue)] +pub struct GetSharedSecretParametersResponse { + pub ret: SharedSecretParameters, +} +#[derive(Debug, AsCborValue)] +pub struct ComputeSharedSecretRequest { + pub params: Vec, +} +#[derive(Debug, AsCborValue)] +pub struct ComputeSharedSecretResponse { + pub ret: Vec, +} + +// ISecureClock methods. +#[derive(Debug, AsCborValue)] +pub struct GenerateTimeStampRequest { + pub challenge: i64, +} +#[derive(Debug, AsCborValue)] +pub struct GenerateTimeStampResponse { + pub ret: TimeStampToken, +} + +// The following messages have no equivalent on a HAL interface, but are used internally +// between components. + +// HAL->TA at start of day. +#[derive(Debug, PartialEq, Eq, AsCborValue)] +pub struct SetHalInfoRequest { + pub os_version: u32, + pub os_patchlevel: u32, // YYYYMM format + pub vendor_patchlevel: u32, // YYYYMMDD format +} +#[derive(Debug, AsCborValue)] +pub struct SetHalInfoResponse {} + +// HAL->TA at start of day. +#[derive(Debug, PartialEq, Eq, AsCborValue)] +pub struct SetHalVersionRequest { + pub aidl_version: u32, +} +#[derive(Debug, AsCborValue)] +pub struct SetHalVersionResponse {} + +// Boot loader->TA at start of day. +#[derive(Debug, AsCborValue)] +pub struct SetBootInfoRequest { + pub verified_boot_key: Vec, + pub device_boot_locked: bool, + pub verified_boot_state: i32, + pub verified_boot_hash: Vec, + pub boot_patchlevel: u32, // YYYYMMDD format +} +#[derive(Debug, AsCborValue)] +pub struct SetBootInfoResponse {} + +/// Attestation ID information. +#[derive(Clone, Debug, AsCborValue, PartialEq, Eq, Default)] +pub struct AttestationIdInfo { + // The following fields are byte vectors that typically hold UTF-8 string data. + pub brand: Vec, + pub device: Vec, + pub product: Vec, + pub serial: Vec, + pub imei: Vec, + pub imei2: Vec, + pub meid: Vec, + pub manufacturer: Vec, + pub model: Vec, +} + +// Provisioner->TA at device provisioning time. +#[derive(Debug, AsCborValue)] +pub struct SetAttestationIdsRequest { + pub ids: AttestationIdInfo, +} +#[derive(Debug, AsCborValue)] +pub struct SetAttestationIdsResponse {} + +// Result of an operation, as an error code and a response message (only present when +// `error_code` is zero). +#[derive(AsCborValue, Debug)] +pub struct PerformOpResponse { + pub error_code: i32, + pub rsp: Option, +} + +/// Declare a collection of related enums for a code and a pair of types. +/// +/// An invocation like: +/// ```ignore +/// declare_req_rsp_enums! { KeyMintOperation => (PerformOpReq, PerformOpRsp) { +/// DeviceGetHardwareInfo = 0x11 => (GetHardwareInfoRequest, GetHardwareInfoResponse), +/// DeviceAddRngEntropy = 0x12 => (AddRngEntropyRequest, AddRngEntropyResponse), +/// } } +/// ``` +/// will emit three `enum` types all of whose variant names are the same (taken from the leftmost +/// column), but whose contents are: +/// +/// - the numeric values (second column) +/// ```ignore +/// #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +/// enum KeyMintOperation { +/// DeviceGetHardwareInfo = 0x11, +/// DeviceAddRngEntropy = 0x12, +/// } +/// ``` +/// +/// - the types from the third column: +/// ```ignore +/// #[derive(Debug)] +/// enum PerformOpReq { +/// DeviceGetHardwareInfo(GetHardwareInfoRequest), +/// DeviceAddRngEntropy(AddRngEntropyRequest), +/// } +/// ``` +/// +/// - the types from the fourth column: +/// ```ignore +/// #[derive(Debug)] +/// enum PerformOpRsp { +/// DeviceGetHardwareInfo(GetHardwareInfoResponse), +/// DeviceAddRngEntropy(AddRngEntropyResponse), +/// } +// ``` +/// +/// Each of these enum types will also get an implementation of [`AsCborValue`] +macro_rules! declare_req_rsp_enums { + { + $cenum:ident => ($reqenum:ident, $rspenum:ident) + { + $( $cname:ident = $cvalue:expr => ($reqtyp:ty, $rsptyp:ty) , )* + } + } => { + declare_req_rsp_enums! { $cenum => ($reqenum, $rspenum) + ( concat!("&(\n", + $( " [", stringify!($cname), ", {}],\n", )* + ")") ) + { + $( $cname = $cvalue => ($reqtyp, $rsptyp), )* + } } + }; + { + $cenum:ident => ($reqenum:ident, $rspenum:ident) ( $cddlfmt:expr ) + { + $( $cname:ident = $cvalue:expr => ($reqtyp:ty, $rsptyp:ty) , )* + } + } => { + + #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, N)] + pub enum $cenum { + $( $cname = $cvalue, )* + } + + impl AsCborValue for $cenum { + /// Create an instance of the enum from a [`cbor::value::Value`], checking that the + /// value is valid. + fn from_cbor_value(value: $crate::cbor::value::Value) -> + Result { + use core::convert::TryInto; + // First get the int value as an `i32`. + let v: i32 = match value { + $crate::cbor::value::Value::Integer(i) => i.try_into().map_err(|_| { + crate::CborError::OutOfRangeIntegerValue + })?, + v => return crate::cbor_type_error(&v, &"int"), + }; + // Now check it is one of the defined enum values. + Self::n(v).ok_or(crate::CborError::NonEnumValue) + } + /// Convert the enum value to a [`cbor::value::Value`] (without checking that the + /// contained enum value is valid). + fn to_cbor_value(self) -> Result<$crate::cbor::value::Value, crate::CborError> { + Ok($crate::cbor::value::Value::Integer((self as i64).into())) + } + fn cddl_typename() -> Option { + Some(stringify!($cenum).to_string()) + } + fn cddl_schema() -> Option { + Some( concat!("&(\n", + $( " ", stringify!($cname), ": ", stringify!($cvalue), ",\n", )* + ")").to_string() ) + } + } + + #[derive(Debug)] + pub enum $reqenum { + $( $cname($reqtyp), )* + } + + impl $reqenum { + pub fn code(&self) -> $cenum { + match self { + $( Self::$cname(_) => $cenum::$cname, )* + } + } + } + + #[derive(Debug)] + pub enum $rspenum { + $( $cname($rsptyp), )* + } + + impl AsCborValue for $reqenum { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let mut a = match value { + cbor::value::Value::Array(a) => a, + _ => return crate::cbor_type_error(&value, "arr"), + }; + if a.len() != 2 { + return Err(CborError::UnexpectedItem("arr", "arr len 2")); + } + let ret_val = a.remove(1); + let ret_type = <$cenum>::from_cbor_value(a.remove(0))?; + match ret_type { + $( $cenum::$cname => Ok(Self::$cname(<$reqtyp>::from_cbor_value(ret_val)?)), )* + } + } + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Array(match self { + $( Self::$cname(val) => { + vec_try![ + $cenum::$cname.to_cbor_value()?, + val.to_cbor_value()? + ]? + }, )* + })) + } + + fn cddl_typename() -> Option { + Some(stringify!($reqenum).to_string()) + } + + fn cddl_schema() -> Option { + Some(format!($cddlfmt, + $( <$reqtyp>::cddl_ref(), )* + )) + } + } + + impl AsCborValue for $rspenum { + fn from_cbor_value(value: cbor::value::Value) -> Result { + let mut a = match value { + cbor::value::Value::Array(a) => a, + _ => return crate::cbor_type_error(&value, "arr"), + }; + if a.len() != 2 { + return Err(CborError::UnexpectedItem("arr", "arr len 2")); + } + let ret_val = a.remove(1); + let ret_type = <$cenum>::from_cbor_value(a.remove(0))?; + match ret_type { + $( $cenum::$cname => Ok(Self::$cname(<$rsptyp>::from_cbor_value(ret_val)?)), )* + } + } + fn to_cbor_value(self) -> Result { + Ok(cbor::value::Value::Array(match self { + $( Self::$cname(val) => { + vec_try![ + $cenum::$cname.to_cbor_value()?, + val.to_cbor_value()? + ]? + }, )* + })) + } + + fn cddl_typename() -> Option { + Some(stringify!($rspenum).to_string()) + } + + fn cddl_schema() -> Option { + Some(format!($cddlfmt, + $( <$rsptyp>::cddl_ref(), )* + )) + } + } + + $( + impl Code<$cenum> for $reqtyp { + const CODE: $cenum = $cenum::$cname; + } + )* + + $( + impl Code<$cenum> for $rsptyp { + const CODE: $cenum = $cenum::$cname; + } + )* + }; +} + +// Possible KeyMint operation requests, as: +// - an enum value with an explicit numeric value +// - a request enum which has an operation code associated to each variant +// - a response enum which has the same operation code associated to each variant. +declare_req_rsp_enums! { KeyMintOperation => (PerformOpReq, PerformOpRsp) { + DeviceGetHardwareInfo = 0x11 => (GetHardwareInfoRequest, GetHardwareInfoResponse), + DeviceAddRngEntropy = 0x12 => (AddRngEntropyRequest, AddRngEntropyResponse), + DeviceGenerateKey = 0x13 => (GenerateKeyRequest, GenerateKeyResponse), + DeviceImportKey = 0x14 => (ImportKeyRequest, ImportKeyResponse), + DeviceImportWrappedKey = 0x15 => (ImportWrappedKeyRequest, ImportWrappedKeyResponse), + DeviceUpgradeKey = 0x16 => (UpgradeKeyRequest, UpgradeKeyResponse), + DeviceDeleteKey = 0x17 => (DeleteKeyRequest, DeleteKeyResponse), + DeviceDeleteAllKeys = 0x18 => (DeleteAllKeysRequest, DeleteAllKeysResponse), + DeviceDestroyAttestationIds = 0x19 => (DestroyAttestationIdsRequest, DestroyAttestationIdsResponse), + DeviceBegin = 0x1a => (BeginRequest, BeginResponse), + // 0x1b used to be DeviceDeviceLocked, but it was never used and consequently was removed. + DeviceEarlyBootEnded = 0x1c => (EarlyBootEndedRequest, EarlyBootEndedResponse), + DeviceConvertStorageKeyToEphemeral = 0x1d => (ConvertStorageKeyToEphemeralRequest, ConvertStorageKeyToEphemeralResponse), + DeviceGetKeyCharacteristics = 0x1e => (GetKeyCharacteristicsRequest, GetKeyCharacteristicsResponse), + OperationUpdateAad = 0x31 => (UpdateAadRequest, UpdateAadResponse), + OperationUpdate = 0x32 => (UpdateRequest, UpdateResponse), + OperationFinish = 0x33 => (FinishRequest, FinishResponse), + OperationAbort = 0x34 => (AbortRequest, AbortResponse), + RpcGetHardwareInfo = 0x41 => (GetRpcHardwareInfoRequest, GetRpcHardwareInfoResponse), + RpcGenerateEcdsaP256KeyPair = 0x42 => (GenerateEcdsaP256KeyPairRequest, GenerateEcdsaP256KeyPairResponse), + RpcGenerateCertificateRequest = 0x43 => (GenerateCertificateRequestRequest, GenerateCertificateRequestResponse), + RpcGenerateCertificateV2Request = 0x44 => (GenerateCertificateRequestV2Request, GenerateCertificateRequestV2Response), + SharedSecretGetSharedSecretParameters = 0x51 => (GetSharedSecretParametersRequest, GetSharedSecretParametersResponse), + SharedSecretComputeSharedSecret = 0x52 => (ComputeSharedSecretRequest, ComputeSharedSecretResponse), + SecureClockGenerateTimeStamp = 0x61 => (GenerateTimeStampRequest, GenerateTimeStampResponse), + GetRootOfTrustChallenge = 0x71 => (GetRootOfTrustChallengeRequest, GetRootOfTrustChallengeResponse), + GetRootOfTrust = 0x72 => (GetRootOfTrustRequest, GetRootOfTrustResponse), + SendRootOfTrust = 0x73 => (SendRootOfTrustRequest, SendRootOfTrustResponse), + SetHalInfo = 0x81 => (SetHalInfoRequest, SetHalInfoResponse), + SetBootInfo = 0x82 => (SetBootInfoRequest, SetBootInfoResponse), + SetAttestationIds = 0x83 => (SetAttestationIdsRequest, SetAttestationIdsResponse), + SetHalVersion = 0x84 => (SetHalVersionRequest, SetHalVersionResponse), + SetAdditionalAttestationInfo = 0x91 => (SetAdditionalAttestationInfoRequest, SetAdditionalAttestationInfoResponse), +} } + +/// Indicate whether an operation is part of the `IRemotelyProvisionedComponent` HAL. +pub fn is_rpc_operation(code: KeyMintOperation) -> bool { + matches!( + code, + KeyMintOperation::RpcGetHardwareInfo + | KeyMintOperation::RpcGenerateEcdsaP256KeyPair + | KeyMintOperation::RpcGenerateCertificateRequest + | KeyMintOperation::RpcGenerateCertificateV2Request + ) +} From 97fc25815694f8a1b966e9ce2d5aa037e71a2819 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Thu, 2 Oct 2025 20:21:38 +0800 Subject: [PATCH 02/46] init basic Android project structure Signed-off-by: qwq233 --- build.gradle.kts | 85 ++++++++++++++++++ gradle/libs.versions.toml | 20 +++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 7 ++ settings.gradle.kts | 35 ++++++++ src/main/AndroidManifest.xml | 2 + .../java/top/qwq2333/ohmykeymint/placeholder | 1 + 7 files changed, 150 insertions(+) create mode 100644 build.gradle.kts create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 settings.gradle.kts create mode 100644 src/main/AndroidManifest.xml create mode 100644 src/main/java/top/qwq2333/ohmykeymint/placeholder diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..da9e8fb --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,85 @@ +@file:Suppress("UnstableApiUsage") + +import com.android.build.api.variant.BuildConfigField +import com.android.build.api.variant.FilterConfiguration +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties +import java.text.SimpleDateFormat +import java.util.Date + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.serialization) + alias(libs.plugins.rust) +} + +cargo { + module = "./libs/rust" + libname = "keymint" + targets = listOf("arm64", "arm") + + prebuiltToolchains = true + profile = "release" +} + +dependencies { + implementation(libs.kotlin.stdlib.common) + implementation(libs.kotlin.stdlib) + implementation(libs.kotlinx.serialization.json) +} + +android { + defaultConfig.applicationId = "top.qwq2333.ohmykeymint" + namespace = "top.qwq2333.ohmykeymint" + + sourceSets.getByName("main") { + java.srcDir("src/main/java") + } + + lint { + checkReleaseBuilds = true + disable += listOf( + "MissingTranslation", "ExtraTranslation", "BlockedPrivateApi" + ) + } + + packaging { + resources.excludes += "**" + } + + kotlin { + jvmToolchain(Version.java.toString().toInt()) + } + + buildTypes { + getByName("release") { + signingConfig = signingConfigs.getByName("debug") + isMinifyEnabled = false + isShrinkResources = true + } + + getByName("debug") { + isDefault = true + isDebuggable = true + isJniDebuggable = true + } + } + + buildFeatures { + buildConfig = true + } + + defaultConfig { + buildConfigField("String", "BUILD_TIME", "\"${SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())}\"") + } + + applicationVariants.all { + outputs.all { + val abi = this.filters.find { it.filterType == FilterConfiguration.FilterType.ABI.name }?.identifier + val output = this as? com.android.build.gradle.internal.api.BaseVariantOutputImpl + val outputFileName = "OhMyKeymint-${defaultConfig.versionName}-${abiName[abi]}.apk" + output?.outputFileName = outputFileName + } + } +} + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..07c365d --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,20 @@ +[versions] +agp = "8.12.0" +kotlin = "2.2.20" +kotlinxCoroutinesAndroid = "1.10.2" +kotlinxSerializationJson = "1.9.0" +rust = "0.9.6" + +[libraries] +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlin-stdlib-common = { module = "org.jetbrains.kotlin:kotlin-stdlib-common", version.ref = "kotlin" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +rust = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rust" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d64cd4917707c1f8861d8cb53dd15194d4248596 GIT binary patch literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2e11132 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ec6f217 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,35 @@ +@file:Suppress("UnstableApiUsage") +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + gradlePluginPortal() + + maven("https://jitpack.io") + } +} + +plugins { + id("com.gradle.develocity") version "3.19.2" +} + +develocity { + buildScan { + publishing.onlyIf { + System.getenv("GITHUB_ACTIONS") == "true" || it.buildResult.failures.isNotEmpty() + } + termsOfUseAgree.set("yes") + termsOfUseUrl.set("https://gradle.com/terms-of-service") + } +} + +rootProject.name = "OhMyKeymint" diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml new file mode 100644 index 0000000..568741e --- /dev/null +++ b/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/java/top/qwq2333/ohmykeymint/placeholder b/src/main/java/top/qwq2333/ohmykeymint/placeholder new file mode 100644 index 0000000..40816a2 --- /dev/null +++ b/src/main/java/top/qwq2333/ohmykeymint/placeholder @@ -0,0 +1 @@ +Hi \ No newline at end of file From 5f23949cbb613946f9ba32474cfa96d357ab1c08 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 4 Oct 2025 02:28:02 +0800 Subject: [PATCH 03/46] update stubs and init keymaster Signed-off-by: qwq233 --- .gitignore | 10 + libs/rust/.gitignore | 2 + libs/rust/Cargo.toml | 6 + .../hardware/security/keymint/Algorithm.aidl | 37 + .../security/keymint/AttestationKey.aidl | 45 + .../security/keymint/BeginResult.aidl | 41 + .../hardware/security/keymint/BlockMode.aidl | 39 + .../security/keymint/Certificate.aidl | 29 + .../hardware/security/keymint/Digest.aidl | 33 + .../hardware/security/keymint/EcCurve.aidl | 31 + .../hardware/security/keymint/ErrorCode.aidl | 119 ++ .../security/keymint/HardwareAuthToken.aidl | 85 ++ .../keymint/HardwareAuthenticatorType.aidl | 33 + .../security/keymint/IKeyMintDevice.aidl | 983 ++++++++++++++++ .../security/keymint/IKeyMintOperation.aidl | 283 +++++ .../security/keymint/KeyCharacteristics.aidl | 48 + .../security/keymint/KeyCreationResult.aidl | 243 ++++ .../hardware/security/keymint/KeyFormat.aidl | 34 + .../security/keymint/KeyMintHardwareInfo.aidl | 55 + .../hardware/security/keymint/KeyOrigin.aidl | 46 + .../security/keymint/KeyParameter.aidl | 32 + .../security/keymint/KeyParameterValue.aidl | 54 + .../hardware/security/keymint/KeyPurpose.aidl | 53 + .../security/keymint/PaddingMode.aidl | 35 + .../security/keymint/SecurityLevel.aidl | 78 ++ .../hardware/security/keymint/Tag.aidl | 1017 +++++++++++++++++ .../hardware/security/keymint/TagType.aidl | 62 + .../security/secureclock/ISecureClock.aidl | 52 + .../security/secureclock/TimeStampToken.aidl | 56 + .../security/secureclock/Timestamp.aidl | 30 + .../system/keystore2/AuthenticatorSpec.aidl | 38 + .../system/keystore2/Authorization.aidl | 27 + .../keystore2/CreateOperationResponse.aidl | 49 + .../aidl/android/system/keystore2/Domain.aidl | 28 + .../EphemeralStorageKeyResponse.aidl | 35 + .../system/keystore2/IKeystoreOperation.aidl | 120 ++ .../keystore2/IKeystoreSecurityLevel.aidl | 219 ++++ .../system/keystore2/IKeystoreService.aidl | 267 +++++ .../system/keystore2/KeyDescriptor.aidl | 66 ++ .../system/keystore2/KeyEntryResponse.aidl | 43 + .../android/system/keystore2/KeyMetadata.aidl | 63 + .../system/keystore2/KeyParameters.aidl | 25 + .../system/keystore2/KeyPermission.aidl | 106 ++ .../system/keystore2/OperationChallenge.aidl | 28 + .../system/keystore2/ResponseCode.aidl | 144 +++ libs/rust/build.rs | 41 +- libs/rust/src/keymaster/database/mod.rs | 1 + libs/rust/src/keymaster/database/perboot.rs | 107 ++ libs/rust/src/keymaster/db.rs | 298 +++++ libs/rust/src/keymaster/mod.rs | 2 + libs/rust/src/{ => keymint}/attest.rs | 0 libs/rust/src/{ => keymint}/clock.rs | 0 libs/rust/src/keymint/mod.rs | 5 + libs/rust/src/{ => keymint}/rpc.rs | 0 libs/rust/src/{ => keymint}/sdd.rs | 0 libs/rust/src/{ => keymint}/soft.rs | 4 +- libs/rust/src/main.rs | 50 +- libs/rust/src/utils.rs | 10 + 58 files changed, 5407 insertions(+), 40 deletions(-) create mode 100644 libs/rust/aidl/android/hardware/security/keymint/Algorithm.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/AttestationKey.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/BeginResult.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/BlockMode.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/Certificate.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/Digest.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/EcCurve.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/ErrorCode.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyCreationResult.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyFormat.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyOrigin.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyParameter.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyParameterValue.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/KeyPurpose.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/PaddingMode.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/SecurityLevel.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/Tag.aidl create mode 100644 libs/rust/aidl/android/hardware/security/keymint/TagType.aidl create mode 100644 libs/rust/aidl/android/hardware/security/secureclock/ISecureClock.aidl create mode 100644 libs/rust/aidl/android/hardware/security/secureclock/TimeStampToken.aidl create mode 100644 libs/rust/aidl/android/hardware/security/secureclock/Timestamp.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/AuthenticatorSpec.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/Authorization.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/CreateOperationResponse.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/Domain.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/IKeystoreOperation.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/IKeystoreService.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/KeyDescriptor.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/KeyEntryResponse.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/KeyMetadata.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/KeyParameters.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/KeyPermission.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/OperationChallenge.aidl create mode 100644 libs/rust/aidl/android/system/keystore2/ResponseCode.aidl create mode 100644 libs/rust/src/keymaster/database/mod.rs create mode 100644 libs/rust/src/keymaster/database/perboot.rs create mode 100644 libs/rust/src/keymaster/db.rs create mode 100644 libs/rust/src/keymaster/mod.rs rename libs/rust/src/{ => keymint}/attest.rs (100%) rename libs/rust/src/{ => keymint}/clock.rs (100%) create mode 100644 libs/rust/src/keymint/mod.rs rename libs/rust/src/{ => keymint}/rpc.rs (100%) rename libs/rust/src/{ => keymint}/sdd.rs (100%) rename libs/rust/src/{ => keymint}/soft.rs (95%) create mode 100644 libs/rust/src/utils.rs diff --git a/.gitignore b/.gitignore index e69de29..2a95120 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/libs/rust/.gitignore b/libs/rust/.gitignore index ee300e7..7da0ed1 100644 --- a/libs/rust/.gitignore +++ b/libs/rust/.gitignore @@ -18,3 +18,5 @@ Cargo.lock *.pdb /omk + +src/aidl.rs \ No newline at end of file diff --git a/libs/rust/Cargo.toml b/libs/rust/Cargo.toml index 23cc583..9578185 100644 --- a/libs/rust/Cargo.toml +++ b/libs/rust/Cargo.toml @@ -30,6 +30,11 @@ prost-types = "0.14.1" hex = "0.4.3" log4rs = "1.4.0" log = "0.4.28" +rsbinder = "0.4.2" +rusqlite = { version = "0.37.0", features = ["bundled"] } +anyhow = "1.0.100" +async-trait = "0.1.89" +tokio = { version = "1.47.1", features = ["macros", "libc"] } [target.'cfg(target_os = "android")'.dependencies] android_logger_lite = "0.1.0" @@ -64,3 +69,4 @@ kmr-wire = { path = "wire" } [build-dependencies] prost-build = "0.14.1" +rsbinder-aidl = "0.4.2" diff --git a/libs/rust/aidl/android/hardware/security/keymint/Algorithm.aidl b/libs/rust/aidl/android/hardware/security/keymint/Algorithm.aidl new file mode 100644 index 0000000..1820893 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/Algorithm.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Algorithms provided by IKeyMintDevice implementations. + * @hide + */ +@VintfStability +@Backing(type="int") +enum Algorithm { + /** Asymmetric algorithms. */ + RSA = 1, + /** 2 removed, do not reuse. */ + EC = 3, + + /** Block cipher algorithms */ + AES = 32, + TRIPLE_DES = 33, + + /** MAC algorithms */ + HMAC = 128, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/AttestationKey.aidl b/libs/rust/aidl/android/hardware/security/keymint/AttestationKey.aidl new file mode 100644 index 0000000..4e3008f --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/AttestationKey.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.KeyParameter; + +/** + * Contains a key blob with Tag::ATTEST_KEY that can be used to sign an attestation certificate, + * and the DER-encoded X.501 Subject Name that will be placed in the Issuer field of the attestation + * certificate. + * @hide + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable AttestationKey { + /** + * Key blob containing a key pair with KeyPurpose::ATTEST_KEY + */ + byte[] keyBlob; + + /** + * Key parameters needed to use the key in keyBlob, notably Tag::APPLICATION_ID and + * Tag::APPLICATION_DATA, if they were provided during generation of the key in keyBlob. + */ + KeyParameter[] attestKeyParams; + + /** + * The issuerSubjectName to use in the generated attestation. + */ + byte[] issuerSubjectName; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/BeginResult.aidl b/libs/rust/aidl/android/hardware/security/keymint/BeginResult.aidl new file mode 100644 index 0000000..b5336b9 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/BeginResult.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.IKeyMintOperation; +import android.hardware.security.keymint.KeyParameter; + +/** + * This is all the results returned by the IKeyMintDevice begin() function. + * @hide + */ +@VintfStability +parcelable BeginResult { + /** + * This is the challenge used to verify authorization of an operation. + * See IKeyMintOperation.aidl entrypoints updateAad() and update(). + */ + long challenge; + + /** + * begin() uses this field to return additional data from the operation + * initialization, notably to return the IV or nonce from operations + * that generate an IV or nonce. + */ + KeyParameter[] params; + IKeyMintOperation operation; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/BlockMode.aidl b/libs/rust/aidl/android/hardware/security/keymint/BlockMode.aidl new file mode 100644 index 0000000..749da81 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/BlockMode.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Symmetric block cipher modes provided by IKeyMintDevice implementations. + * @hide + */ +@VintfStability +@Backing(type="int") +enum BlockMode { + /* + * Unauthenticated modes, usable only for encryption/decryption and not generally recommended + * except for compatibility with existing other protocols. + */ + ECB = 1, + CBC = 2, + CTR = 3, + + /* + * Authenticated modes, usable for encryption/decryption and signing/verification. Recommended + * over unauthenticated modes for all purposes. + */ + GCM = 32, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/Certificate.aidl b/libs/rust/aidl/android/hardware/security/keymint/Certificate.aidl new file mode 100644 index 0000000..21dfdd5 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/Certificate.aidl @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * This encodes an IKeyMintDevice certificate, generated for a KeyMint asymmetric public key. + * @hide + */ +@VintfStability +parcelable Certificate { + /** + * EncodedCertificate contains the bytes of a DER-encoded X.509 certificate. + */ + byte[] encodedCertificate; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/Digest.aidl b/libs/rust/aidl/android/hardware/security/keymint/Digest.aidl new file mode 100644 index 0000000..a8768c3 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/Digest.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Digests provided by keyMint implementations. + * @hide + */ +@VintfStability +@Backing(type="int") +enum Digest { + NONE = 0, + MD5 = 1, + SHA1 = 2, + SHA_2_224 = 3, + SHA_2_256 = 4, + SHA_2_384 = 5, + SHA_2_512 = 6, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/EcCurve.aidl b/libs/rust/aidl/android/hardware/security/keymint/EcCurve.aidl new file mode 100644 index 0000000..e9f81d8 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/EcCurve.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Supported EC curves, used in ECDSA + * @hide + */ +@VintfStability +@Backing(type="int") +enum EcCurve { + P_224 = 0, + P_256 = 1, + P_384 = 2, + P_521 = 3, + CURVE_25519 = 4, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/ErrorCode.aidl b/libs/rust/aidl/android/hardware/security/keymint/ErrorCode.aidl new file mode 100644 index 0000000..72fa773 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/ErrorCode.aidl @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * KeyMint error codes. Aidl will return these error codes as service specific + * errors in EX_SERVICE_SPECIFIC. + * @hide + */ +@VintfStability +@Backing(type="int") +enum ErrorCode { + OK = 0, + ROOT_OF_TRUST_ALREADY_SET = -1, + UNSUPPORTED_PURPOSE = -2, + INCOMPATIBLE_PURPOSE = -3, + UNSUPPORTED_ALGORITHM = -4, + INCOMPATIBLE_ALGORITHM = -5, + UNSUPPORTED_KEY_SIZE = -6, + UNSUPPORTED_BLOCK_MODE = -7, + INCOMPATIBLE_BLOCK_MODE = -8, + UNSUPPORTED_MAC_LENGTH = -9, + UNSUPPORTED_PADDING_MODE = -10, + INCOMPATIBLE_PADDING_MODE = -11, + UNSUPPORTED_DIGEST = -12, + INCOMPATIBLE_DIGEST = -13, + INVALID_EXPIRATION_TIME = -14, + INVALID_USER_ID = -15, + INVALID_AUTHORIZATION_TIMEOUT = -16, + UNSUPPORTED_KEY_FORMAT = -17, + INCOMPATIBLE_KEY_FORMAT = -18, + UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19, /** For PKCS8 & PKCS12 */ + UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20, /** For PKCS8 & PKCS12 */ + INVALID_INPUT_LENGTH = -21, + KEY_EXPORT_OPTIONS_INVALID = -22, + DELEGATION_NOT_ALLOWED = -23, + KEY_NOT_YET_VALID = -24, + KEY_EXPIRED = -25, + KEY_USER_NOT_AUTHENTICATED = -26, + OUTPUT_PARAMETER_NULL = -27, + INVALID_OPERATION_HANDLE = -28, + INSUFFICIENT_BUFFER_SPACE = -29, + VERIFICATION_FAILED = -30, + TOO_MANY_OPERATIONS = -31, + UNEXPECTED_NULL_POINTER = -32, + INVALID_KEY_BLOB = -33, + IMPORTED_KEY_NOT_ENCRYPTED = -34, + IMPORTED_KEY_DECRYPTION_FAILED = -35, + IMPORTED_KEY_NOT_SIGNED = -36, + IMPORTED_KEY_VERIFICATION_FAILED = -37, + INVALID_ARGUMENT = -38, + UNSUPPORTED_TAG = -39, + INVALID_TAG = -40, + MEMORY_ALLOCATION_FAILED = -41, + IMPORT_PARAMETER_MISMATCH = -44, + SECURE_HW_ACCESS_DENIED = -45, + OPERATION_CANCELLED = -46, + CONCURRENT_ACCESS_CONFLICT = -47, + SECURE_HW_BUSY = -48, + SECURE_HW_COMMUNICATION_FAILED = -49, + UNSUPPORTED_EC_FIELD = -50, + MISSING_NONCE = -51, + INVALID_NONCE = -52, + MISSING_MAC_LENGTH = -53, + KEY_RATE_LIMIT_EXCEEDED = -54, + CALLER_NONCE_PROHIBITED = -55, + KEY_MAX_OPS_EXCEEDED = -56, + INVALID_MAC_LENGTH = -57, + MISSING_MIN_MAC_LENGTH = -58, + UNSUPPORTED_MIN_MAC_LENGTH = -59, + UNSUPPORTED_KDF = -60, + UNSUPPORTED_EC_CURVE = -61, + KEY_REQUIRES_UPGRADE = -62, + ATTESTATION_CHALLENGE_MISSING = -63, + KEYMINT_NOT_CONFIGURED = -64, + ATTESTATION_APPLICATION_ID_MISSING = -65, + CANNOT_ATTEST_IDS = -66, + ROLLBACK_RESISTANCE_UNAVAILABLE = -67, + HARDWARE_TYPE_UNAVAILABLE = -68, + PROOF_OF_PRESENCE_REQUIRED = -69, + CONCURRENT_PROOF_OF_PRESENCE_REQUESTED = -70, + NO_USER_CONFIRMATION = -71, + DEVICE_LOCKED = -72, + EARLY_BOOT_ENDED = -73, + ATTESTATION_KEYS_NOT_PROVISIONED = -74, + ATTESTATION_IDS_NOT_PROVISIONED = -75, + INVALID_OPERATION = -76, + STORAGE_KEY_UNSUPPORTED = -77, + INCOMPATIBLE_MGF_DIGEST = -78, + UNSUPPORTED_MGF_DIGEST = -79, + MISSING_NOT_BEFORE = -80, + MISSING_NOT_AFTER = -81, + MISSING_ISSUER_SUBJECT = -82, + INVALID_ISSUER_SUBJECT = -83, + BOOT_LEVEL_EXCEEDED = -84, + HARDWARE_NOT_YET_AVAILABLE = -85, + MODULE_HASH_ALREADY_SET = -86, + + UNIMPLEMENTED = -100, + VERSION_MISMATCH = -101, + + UNKNOWN_ERROR = -1000, + + // Implementer's namespace for error codes starts at -10000. +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl b/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl new file mode 100644 index 0000000..0933bd5 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl @@ -0,0 +1,85 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.HardwareAuthenticatorType; +import android.hardware.security.secureclock.Timestamp; + +/** + * HardwareAuthToken is used to prove successful user authentication, to unlock the use of a key. + * + * HardwareAuthTokens are produced by other secure environment applications, notably GateKeeper and + * biometric authenticators, in response to successful user authentication events. These tokens are + * passed to begin(), update(), and finish() to prove that authentication occurred. See those + * methods for more details. It is up to the caller to determine which of the generated auth tokens + * is appropriate for a given key operation. + * @hide + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable HardwareAuthToken { + /** + * challenge is a value that's used to enable authentication tokens to authorize specific + * events. The primary use case for challenge is to authorize an IKeyMintDevice cryptographic + * operation, for keys that require authentication per operation. See begin() for details. + */ + long challenge; + + /** + * userId is the a "secure" user ID. It is not related to any Android user ID or UID, but is + * created in the Gatekeeper application in the secure environment. + */ + long userId; + + /** + * authenticatorId is the a "secure" user ID. It is not related to any Android user ID or UID, + * but is created in an authentication application in the secure environment, such as the + * Fingerprint application. + */ + long authenticatorId; + + /** + * authenticatorType describes the type of authentication that took place, e.g. password or + * fingerprint. + */ + HardwareAuthenticatorType authenticatorType = HardwareAuthenticatorType.NONE; + + /** + * timestamp indicates when the user authentication took place, in milliseconds since some + * starting point (generally the most recent device boot) which all of the applications within + * one secure environment must agree upon. This timestamp is used to determine whether or not + * the authentication occurred recently enough to unlock a key (see Tag::AUTH_TIMEOUT). + */ + Timestamp timestamp; + + /** + * MACs are computed with a backward-compatible method, used by Keymaster 3.0, Gatekeeper 1.0 + * and Fingerprint 1.0, as well as pre-treble HALs. + * + * The MAC is Constants::AUTH_TOKEN_MAC_LENGTH bytes in length and is computed as follows: + * + * HMAC_SHA256( + * H, 0 || challenge || user_id || authenticator_id || authenticator_type || timestamp) + * + * where ``||'' represents concatenation, the leading zero is a single byte, and all integers + * are represented as unsigned values, the full width of the type. The challenge, userId and + * authenticatorId values are in machine order, but authenticatorType and timestamp are in + * network order (big-endian). This odd construction is compatible with the hw_auth_token_t + * structure. + */ + byte[] mac; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl b/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl new file mode 100644 index 0000000..2d9d0ff --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl @@ -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. + */ + +package android.hardware.security.keymint; + +/** + * Hardware authentication type, used by HardwareAuthTokens to specify the mechanism used to + * authentiate the user, and in KeyCharacteristics to specify the allowable mechanisms for + * authenticating to activate a key. + * @hide + */ +@VintfStability +@Backing(type="int") +enum HardwareAuthenticatorType { + NONE = 0, + PASSWORD = 1 << 0, + FINGERPRINT = 1 << 1, + // Additional entries must be powers of 2. + ANY = 0xFFFFFFFF, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/libs/rust/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl new file mode 100644 index 0000000..0ae4b96 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl @@ -0,0 +1,983 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.AttestationKey; +import android.hardware.security.keymint.BeginResult; +import android.hardware.security.keymint.HardwareAuthToken; +import android.hardware.security.keymint.IKeyMintOperation; +import android.hardware.security.keymint.KeyCharacteristics; +import android.hardware.security.keymint.KeyCreationResult; +import android.hardware.security.keymint.KeyFormat; +import android.hardware.security.keymint.KeyMintHardwareInfo; +import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.KeyPurpose; +import android.hardware.security.keymint.SecurityLevel; +import android.hardware.security.secureclock.TimeStampToken; + +/** + * KeyMint device definition. + * + * == Features == + * + * An IKeyMintDevice provides cryptographic services, including the following categories of + * operations: + * + * o Key generation + * o Import of asymmetric keys + * o Import of raw symmetric keys + * o Asymmetric decryption with appropriate padding modes + * o Asymmetric signing with digesting and appropriate padding modes + * o Symmetric encryption and decryption in appropriate modes, including an AEAD mode + * o Generation and verification of symmetric message authentication codes + * o Attestation to the presence and configuration of asymmetric keys. + * + * Protocol elements, such as purpose, mode and padding, as well as access control constraints, must + * be specified by the caller when keys are generated or imported and must be permanently bound to + * the key, ensuring that the key cannot be used in any other way. + * + * In addition to the list above, IKeyMintDevice implementations must provide one more service + * which is not exposed as an API but used internally: Random number generation. The random number + * generator must be high-quality and must be used for generation of keys, initialization vectors, + * random padding and other elements of secure protocols that require randomness. + * + * == Types of IKeyMintDevices == + * + * All of the operations and storage of key material must occur in a secure environment. Secure + * environments may be either: + * + * 1. Isolated execution environments, such as a separate virtual machine, hypervisor or + * purpose-built trusted execution environment like ARM TrustZone. The isolated environment + * must provide complete separation from the Android kernel and user space (collectively called + * the "non-secure world", or NSW) so that nothing running in the NSW can observe or manipulate + * the results of any computation in the isolated environment. Isolated execution environments + * are identified by the SecurityLevel TRUSTED_ENVIRONMENT. + * + * 2. Completely separate, purpose-built and certified secure CPUs, called "StrongBox" devices. + * Examples of StrongBox devices are embedded Secure Elements (eSE) or on-SoC secure processing + * units (iSE). StrongBox environments are identified by the SecurityLevel STRONGBOX. To + * qualify as a StrongBox, a device must meet the requirements specified in CDD 9.11.2. + * + * == Necessary Primitives == + * + * All IKeyMintDevice implementations must provide support for the following: + * + * o RSA + * + * - TRUSTED_ENVIRONMENT IKeyMintDevices must support 2048, 3072 and 4096-bit keys. + * STRONGBOX IKeyMintDevices must support 2048-bit keys. + * - Public exponent F4 (2^16+1) + * - Unpadded, RSASSA-PSS and RSASSA-PKCS1-v1_5 padding modes for RSA signing + * - TRUSTED_ENVIRONMENT IKeyMintDevices must support MD5, SHA1, SHA-2 224, SHA-2 256, SHA-2 + * 384 and SHA-2 512 digest modes for RSA signing. STRONGBOX IKeyMintDevices must support + * SHA-2 256. + * - Unpadded, RSAES-OAEP and RSAES-PKCS1-v1_5 padding modes for RSA encryption. + * + * o ECDSA and ECDH + * + * - IKeyMintDevices must support elliptic curve signing (Purpose::SIGN, Purpose::ATTEST_KEY) + * and key agreement operations (Purpose::AGREE_KEY). + * - TRUSTED_ENVIRONMENT IKeyMintDevices must support NIST curves P-224, P-256, P-384 and + * P-521. STRONGBOX IKeyMintDevices must support NIST curve P-256. + * - For signing, TRUSTED_ENVIRONMENT IKeyMintDevices must support SHA1, SHA-2 224, SHA-2 256, + * SHA-2 384 and SHA-2 512 digest modes. STRONGBOX IKeyMintDevices must support SHA-2 256. + * - TRUSTED_ENVRIONMENT IKeyMintDevices must support curve 25519 for Purpose::SIGN (Ed25519, + * as specified in RFC 8032), Purpose::ATTEST_KEY (Ed25519) or for KeyPurpose::AGREE_KEY + * (X25519, as specified in RFC 7748). However, a key must have exactly one of these + * purpose values; the same key cannot be used for multiple purposes. Signing operations + * (Purpose::SIGN) have a message size limit of 16 KiB; operations on messages longer than + * this limit must fail with ErrorCode::INVALID_INPUT_LENGTH. + * STRONGBOX IKeyMintDevices do not support curve 25519. + * + * o AES + * + * - TRUSTED_ENVIRONMENT IKeyMintDevices must support 128, 192 and 256-bit keys. + * STRONGBOX IKeyMintDevices must only support 128 and 256-bit keys. + * - CBC, CTR, ECB and GCM modes. The GCM mode must not allow the use of tags smaller than 96 + * bits or nonce lengths other than 96 bits. + * - CBC and ECB modes must support unpadded and PKCS7 padding modes. With no padding CBC and + * ECB-mode operations must fail with ErrorCode::INVALID_INPUT_LENGTH if the input isn't a + * multiple of the AES block size. With PKCS7 padding, GCM and CTR operations must fail with + * ErrorCode::INCOMPATIBLE_PADDING_MODE. + * + * o 3DES + * + * - 168-bit keys. + * - CBC and ECB mode. + * - CBC and ECB modes must support unpadded and PKCS7 padding modes. With no padding CBC and + * ECB-mode operations must fail with ErrorCode::INVALID_INPUT_LENGTH if the input isn't a + * multiple of the DES block size. + * + * o HMAC + * + * - Any key size that is between 64 and 512 bits (inclusive) and a multiple of 8 must be + * supported. STRONGBOX IKeyMintDevices must not support keys larger than 512 bits. + * - TRUSTED_ENVIRONMENT IKeyMintDevices must support MD-5, SHA1, SHA-2-224, SHA-2-256, + * SHA-2-384 and SHA-2-512. STRONGBOX IKeyMintDevices must support SHA-2-256. + * + * == Key Access Control == + * + * Hardware-based keys that can never be extracted from the device don't provide much security if an + * attacker can use them at will (though they're more secure than keys which can be + * exfiltrated). Therefore, IKeyMintDevice must enforce access controls. + * + * Access controls are defined as "authorization lists" of tag/value pairs. Authorization tags are + * 32-bit integers from the Tag enum, and the values are a variety of types, defined in the TagType + * enum. Some tags may be repeated to specify multiple values. Whether a tag may be repeated is + * specified in the documentation for the tag and in the TagType. When a key is created or + * imported, the caller specifies a `key_description` authorization list. The IKeyMintDevice must + * determine which tags it can and cannot enforce, and at what SecurityLevel, and return an array of + * `KeyCharacteristics` structures that contains everything it will enforce, associated with the + * appropriate security level, which is one of SOFTWARE, TRUSTED_ENVIRONMENT and STRONGBOX. + * Typically, implementations will only return a single KeyCharacteristics structure, because + * everything they enforce is enforced at the same security level. There may be cases, however, for + * which multiple security levels are relevant. One example is that of a StrongBox IKeyMintDevice + * that relies on a TEE to enforce biometric user authentication. In that case, the generate/import + * methods must return two KeyCharacteristics structs, one with SecurityLevel::TRUSTED_ENVIRONMENT + * and the biometric authentication-related tags, and another with SecurityLevel::STRONGBOX and + * everything else. The IKeyMintDevice must also add the following authorizations to the + * appropriate list: + * + * o Tag::OS_VERSION + * o Tag::OS_PATCHLEVEL + * o Tag::VENDOR_PATCHLEVEL + * o Tag::BOOT_PATCHLEVEL + * o Tag::ORIGIN + * + * The IKeyMintDevice must ignore unknown tags. + * + * The caller may provide the current date time in the keyParameter CREATION_DATETIME tag, but + * this is optional and informational only. + * + * All authorization tags and their values enforced by an IKeyMintDevice must be cryptographically + * bound to the private/secret key material such that any modification of the portion of the key + * blob that contains the authorization list makes it impossible for the secure environment to + * obtain the private/secret key material. The recommended approach to meet this requirement is to + * use the full set of authorization tags associated with a key as input to a secure key derivation + * function used to derive a key (the KEK) that is used to encrypt the private/secret key material. + * Note that it is NOT acceptable to use a static KEK to encrypt the private/secret key material + * with an AEAD cipher mode, using the enforced authorization tags as AAD. This is because + * Tag::APPLICATION_DATA must not be included in the authorization tags stored in the key blob, but + * must be provided by the caller for every use. Assuming the Tag::APPLICATION_DATA value has + * sufficient entropy, this provides a cryptographic guarantee that an attacker cannot use a key + * without knowing the Tag::APPLICATION_DATA value, even if they compromise the IKeyMintDevice. + * + * IKeyMintDevice implementations must ignore any tags they cannot enforce and must not return them + * in KeyCharacteristics. For example, Tag::ORIGINATION_EXPIRE_DATETIME provides the date and time + * after which a key may not be used to encrypt or sign new messages. Unless the IKeyMintDevice has + * access to a secure source of current date/time information, it is not possible for the + * IKeyMintDevice to enforce this tag. An IKeyMintDevice implementation will not rely on the + * non-secure world's notion of time, because it could be controlled by an attacker. Similarly, it + * cannot rely on GPSr time, even if it has exclusive control of the GPSr, because that might be + * spoofed by attacker RF signals. + * + * Some tags must be enforced by the IKeyMintDevice. See the detailed documentation on each Tag + * in Tag.aidl. + * + * == Root of Trust Binding == + * + * IKeyMintDevice keys must be bound to a root of trust, which is a bitstring that must be + * provided to the secure environment (by an unspecified, implementation-defined mechanism) during + * startup, preferably by the bootloader. This bitstring must be cryptographically bound to every + * key managed by the IKeyMintDevice. As above, the recommended mechanism for this cryptographic + * binding is to include the Root of Trust data in the input to the key derivation function used to + * derive a key that is used to encrypt the private/secret key material. + * + * The root of trust consists of a bitstring that must be derived from the public key used by + * Verified Boot to verify the signature on the boot image, from the lock state and from the + * Verified Boot state of the device. If the public key is changed to allow a different system + * image to be used or if the lock state is changed, then all of the IKeyMintDevice-protected keys + * created by the previous system state must be unusable, unless the previous state is restored. + * The goal is to increase the value of the software-enforced key access controls by making it + * impossible for an attacker-installed operating system to use IKeyMintDevice keys. + * + * == Version Binding == + * + * All keys must also be bound to the operating system and patch level of the system image and the + * patch levels of the vendor image and boot image. This ensures that an attacker who discovers a + * weakness in an old version of the software cannot roll a device back to the vulnerable version + * and use keys created with the newer version. In addition, when a key with a given version and + * patch level is used on a device that has been upgraded to a newer version or patch level, the + * key must be upgraded (See IKeyMintDevice::upgradeKey()) before it can be used, and the previous + * version of the key must be invalidated. In this way, as the device is upgraded, the keys will + * "ratchet" forward along with the device, but any reversion of the device to a previous release + * will cause the keys to be unusable. + * + * This version information must be associated with every key as a set of tag/value pairs in the + * hardwareEnforced authorization list. Tag::OS_VERSION, Tag::OS_PATCHLEVEL, + * Tag::VENDOR_PATCHLEVEL, and Tag::BOOT_PATCHLEVEL must be cryptographically bound to every + * IKeyMintDevice key, as described in the Key Access Control section above. + * @hide + */ +@VintfStability +@SensitiveData +interface IKeyMintDevice { + const int AUTH_TOKEN_MAC_LENGTH = 32; + + /** + * @return info which contains information about the underlying IKeyMintDevice hardware, such + * as version number, security level, keyMint name and author name. + */ + KeyMintHardwareInfo getHardwareInfo(); + + /** + * Adds entropy to the RNG used by KeyMint. Entropy added through this method must not be the + * only source of entropy used, and a secure mixing function must be used to mix the entropy + * provided by this method with internally-generated entropy. The mixing function must be + * secure in the sense that if any one of the mixing function inputs is provided with any data + * the attacker cannot predict (or control), then the output of the seeded CRNG is + * indistinguishable from random. Thus, if the entropy from any source is good, the output + * must be good. + * + * @param data Bytes to be mixed into the CRNG seed. The caller must not provide more than 2 + * KiB of data per invocation. + * + * @return error ErrorCode::OK on success; ErrorCode::INVALID_INPUT_LENGTH if the caller + * provides more than 2 KiB of data. + */ + void addRngEntropy(in byte[] data); + + /** + * Generates a new cryptographic key, specifying associated parameters, which must be + * cryptographically bound to the key. IKeyMintDevice implementations must disallow any use + * of a key in any way inconsistent with the authorizations specified at generation time. With + * respect to parameters that the secure environment cannot enforce, the secure environment's + * obligation is limited to ensuring that the unenforceable parameters associated with the key + * cannot be modified. In addition, the characteristics returned by generateKey places + * parameters correctly in the tee-enforced and strongbox-enforced lists. + * + * In addition to the parameters provided, generateKey must add the following to the returned + * characteristics. + * + * o Tag::ORIGIN with the value KeyOrigin::GENERATED. + * + * o Tag::OS_VERSION, Tag::OS_PATCHLEVEL, Tag::VENDOR_PATCHLEVEL and Tag::BOOT_PATCHLEVEL with + * appropriate values. + * + * The parameters provided to generateKey depend on the type of key being generated. This + * section summarizes the necessary and optional tags for each type of key. Tag::ALGORITHM is + * always necessary, to specify the type. + * + * == RSA Keys == + * + * The following parameters are required to generate an RSA key: + * + * o Tag::KEY_SIZE specifies the size of the public modulus, in bits. If omitted, generateKey + * must return ErrorCode::UNSUPPORTED_KEY_SIZE. Required values for TEE IKeyMintDevice + * implementations are 1024, 2048, 3072 and 4096. StrongBox IKeyMintDevice implementations + * must support 2048. + * + * o Tag::RSA_PUBLIC_EXPONENT specifies the RSA public exponent value. If omitted, generateKey + * must return ErrorCode::INVALID_ARGUMENT. The values 3 and 65537 must be supported. It is + * recommended to support all prime values up to 2^64. + * + * o Tag::CERTIFICATE_NOT_BEFORE and Tag::CERTIFICATE_NOT_AFTER specify the valid date range for + * the returned X.509 certificate holding the public key. If omitted, generateKey must return + * ErrorCode::MISSING_NOT_BEFORE or ErrorCode::MISSING_NOT_AFTER. + * + * The following parameters are not necessary to generate a usable RSA key, but generateKey must + * not return an error if they are omitted: + * + * o Tag::PURPOSE specifies allowed purposes. All KeyPurpose values (see KeyPurpose.aidl) + * except AGREE_KEY must be supported for RSA keys. + * + * o Tag::DIGEST specifies digest algorithms that may be used with the new key. TEE + * IKeyMintDevice implementations must support all Digest values (see Digest.aidl) for RSA + * keys. StrongBox IKeyMintDevice implementations must support SHA_2_256. + * + * o Tag::PADDING specifies the padding modes that may be used with the new + * key. IKeyMintDevice implementations must support PaddingMode::NONE, + * PaddingMode::RSA_OAEP, PaddingMode::RSA_PSS, PaddingMode::RSA_PKCS1_1_5_ENCRYPT and + * PaddingMode::RSA_PKCS1_1_5_SIGN for RSA keys. + * + * == ECDSA/ECDH Keys == + * + * Tag::EC_CURVE must be provided to generate an elliptic curve key. If it is not provided, + * generateKey must return ErrorCode::UNSUPPORTED_KEY_SIZE or ErrorCode::UNSUPPORTED_EC_CURVE. + * TEE IKeyMintDevice implementations must support all required curves. StrongBox + * implementations must support P_256 and no other curves. + * + * Tag::CERTIFICATE_NOT_BEFORE and Tag::CERTIFICATE_NOT_AFTER must be provided to specify the + * valid date range for the returned X.509 certificate holding the public key. If omitted, + * generateKey must return ErrorCode::MISSING_NOT_BEFORE or ErrorCode::MISSING_NOT_AFTER. + * + * Keys with EC_CURVE of EcCurve::CURVE_25519 must have exactly one purpose in the set + * {KeyPurpose::SIGN, KeyPurpose::ATTEST_KEY, KeyPurpose::AGREE_KEY}. Key generation with more + * than one purpose should be rejected with ErrorCode::INCOMPATIBLE_PURPOSE. + * StrongBox implementation do not support CURVE_25519. + * + * Tag::DIGEST specifies digest algorithms that may be used with the new key when used for + * signing. TEE IKeyMintDevice implementations must support all Digest values (see Digest.aidl) + * for ECDSA keys; Ed25519 keys only support Digest::NONE. StrongBox IKeyMintDevice + * implementations must support SHA_2_256. + * + * == AES Keys == + * + * Only Tag::KEY_SIZE is required to generate an AES key. If omitted, generateKey must return + * ErrorCode::UNSUPPORTED_KEY_SIZE. 128 and 256-bit key sizes must be supported. + * + * If Tag::BLOCK_MODE is specified with value BlockMode::GCM, then the caller must also provide + * Tag::MIN_MAC_LENGTH. If omitted, generateKey must return ErrorCode::MISSING_MIN_MAC_LENGTH. + * + * == 3DES Keys == + * + * Only Tag::KEY_SIZE is required to generate an 3DES key, and its value must be 168. If + * omitted, generateKey must return ErrorCode::UNSUPPORTED_KEY_SIZE. + * + * == HMAC Keys == + * + * Tag::KEY_SIZE must be provided to generate an HMAC key, and its value must be >= 64 and a + * multiple of 8. All devices must support key sizes up to 512 bits, but StrongBox devices must + * not support key sizes larger than 512 bits. If omitted or invalid, generateKey() must return + * ErrorCode::UNSUPPORTED_KEY_SIZE. + * + * Tag::MIN_MAC_LENGTH must be provided, and must be a multiple of 8 in the range 64 to 512 + * bits (inclusive). If omitted, generateKey must return ErrorCode::MISSING_MIN_MAC_LENGTH; if + * invalid, generateKey must return ErrorCode::UNSUPPORTED_MIN_MAC_LENGTH. + * + * @param keyParams Key generation parameters are defined as KeyMintDevice tag/value pairs, + * provided in params. See above for detailed specifications of which tags are required + * for which types of keys. + * + * @param attestationKey, if provided, specifies the key that must be used to sign the + * attestation certificate. If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE + * but `attestationKey` is non-null, the IKeyMintDevice must return + * ErrorCode::ATTESTATION_CHALLENGE_MISSING. If the provided AttestationKey does not + * contain a key blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the + * IKeyMintDevice must return ErrorCode::INCOMPATIBLE_PURPOSE. If the provided + * AttestationKey has an empty issuer subject name, the IKeyMintDevice must return + * ErrorCode::INVALID_ARGUMENT. + * + * If `attestationKey` is null and `keyParams` contains Tag::ATTESTATION_CHALLENGE but + * the KeyMint implementation does not have factory-provisioned attestation keys, it must + * return ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED. + * + * @return The result of key creation. See KeyCreationResult.aidl. + */ + KeyCreationResult generateKey( + in KeyParameter[] keyParams, in @nullable AttestationKey attestationKey); + + /** + * Imports key material into an IKeyMintDevice. Key definition parameters and return values + * are the same as for generateKey, with the following exceptions: + * + * o Tag::KEY_SIZE is not necessary in the input parameters. If not provided, the + * IKeyMintDevice must deduce the value from the provided key material and add the tag and + * value to the key characteristics. If Tag::KEY_SIZE is provided, the IKeyMintDevice must + * validate it against the key material. In the event of a mismatch, importKey must return + * ErrorCode::IMPORT_PARAMETER_MISMATCH. + * + * o Tag::EC_CURVE is not necessary in the input parameters for import of EC keys. If not + * provided the IKeyMintDevice must deduce the value from the provided key material and add + * the tag and value to the key characteristics. If Tag::EC_CURVE is provided, the + * IKeyMintDevice must validate it against the key material. In the event of a mismatch, + * importKey must return ErrorCode::IMPORT_PARAMETER_MISMATCH. + * + * o Tag::RSA_PUBLIC_EXPONENT (for RSA keys only) is not necessary in the input parameters. If + * not provided, the IKeyMintDevice must deduce the value from the provided key material and + * add the tag and value to the key characteristics. If Tag::RSA_PUBLIC_EXPONENT is provided, + * the IKeyMintDevice must validate it against the key material. In the event of a + * mismatch, importKey must return ErrorCode::IMPORT_PARAMETER_MISMATCH. + * + * o Tag::ORIGIN (returned in keyCharacteristics) must have the value KeyOrigin::IMPORTED. + * + * @param keyParams Key generation parameters are defined as KeyMintDevice tag/value pairs, + * provided in params. + * + * @param keyFormat The format of the key material to import. See KeyFormat in keyformat.aidl. + * + * @param keyData The key material to import, in the format specified in keyFormat. + * + * @param attestationKey, if provided, specifies the key that must be used to sign the + * attestation certificate. If `keyParams` does not contain a Tag::ATTESTATION_CHALLENGE + * but `attestationKey` is non-null, the IKeyMintDevice must return + * ErrorCode::INVALID_ARGUMENT. If the provided AttestationKey does not contain a key + * blob containing an asymmetric key with KeyPurpose::ATTEST_KEY, the IKeyMintDevice must + * return ErrorCode::INCOMPATIBLE_PURPOSE. If the provided AttestationKey has an empty + * issuer subject name, the IKeyMintDevice must return ErrorCode::INVALID_ARGUMENT. + * + * If `attestationKey` is null and `keyParams` contains Tag::ATTESTATION_CHALLENGE but + * the KeyMint implementation does not have factory-provisioned attestation keys, it must + * return ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED. + * + * @return The result of key creation. See KeyCreationResult.aidl. + */ + KeyCreationResult importKey(in KeyParameter[] keyParams, in KeyFormat keyFormat, + in byte[] keyData, in @nullable AttestationKey attestationKey); + + /** + * Securely imports a key, or key pair, returning a key blob and a description of the imported + * key. + * + * @param wrappedKeyData The wrapped key material to import, as ASN.1 DER-encoded data + * corresponding to the following schema. + * + * KeyDescription ::= SEQUENCE( + * keyFormat INTEGER, # Values from KeyFormat enum. + * keyParams AuthorizationList, + * ) + * + * SecureKeyWrapper ::= SEQUENCE( + * version INTEGER, # Contains value 0 + * encryptedTransportKey OCTET_STRING, + * initializationVector OCTET_STRING, + * keyDescription KeyDescription, + * encryptedKey OCTET_STRING, + * tag OCTET_STRING + * ) + * + * Where: + * + * - keyFormat is an integer from the KeyFormat enum, defining the format of the plaintext + * key material. + * - keyParams is the characteristics of the key to be imported (as with generateKey or + * importKey). If the secure import is successful, these characteristics must be + * associated with the key exactly as if the key material had been insecurely imported + * with the IKeyMintDevice::importKey. See KeyCreationResult.aidl for documentation of + * the AuthorizationList schema. + * - encryptedTransportKey is a 256-bit AES key, XORed with a masking key and then encrypted + * with the wrapping key specified by wrappingKeyBlob. + * - keyDescription is a KeyDescription, above. + * - encryptedKey is the key material of the key to be imported, in format keyFormat, and + * encrypted with encryptedEphemeralKey in AES-GCM mode, with the DER-encoded + * representation of keyDescription provided as additional authenticated data. + * - tag is the tag produced by the AES-GCM encryption of encryptedKey. + * + * So, importWrappedKey does the following: + * + * 1. Get the private key material for wrappingKeyBlob, verifying that the wrapping key has + * purpose KEY_WRAP, padding mode RSA_OAEP, and digest SHA_2_256, returning the + * error INCOMPATIBLE_PURPOSE, INCOMPATIBLE_PADDING_MODE, or INCOMPATIBLE_DIGEST if any + * of those requirements fail. + * 2. Extract the encryptedTransportKey field from the SecureKeyWrapper, and decrypt + * it with the wrapping key. + * 3. XOR the result of step 2 with maskingKey. + * 4. Use the result of step 3 as an AES-GCM key to decrypt encryptedKey, using the encoded + * value of keyDescription as the additional authenticated data. Call the result + * "keyData" for the next step. + * 5. Perform the equivalent of calling importKey(keyParams, keyFormat, keyData), except + * that the origin tag should be set to SECURELY_IMPORTED. + * + * @param wrappingKeyBlob The opaque key descriptor returned by generateKey() or importKey(). + * This key must have been created with Purpose::WRAP_KEY. + * + * @param maskingKey The 32-byte value XOR'd with the transport key in the SecureWrappedKey + * structure. + * + * @param unwrappingParams must contain any parameters needed to perform the unwrapping + * operation. For example, the padding mode for the RSA wrapping key must be specified + * in this argument. + * + * @param passwordSid specifies the password secure ID (SID) of the user that owns the key being + * installed. If the authorization list in wrappedKeyData contains a + * Tag::USER_SECURE_ID with a value that has the HardwareAuthenticatorType::PASSWORD bit + * set, the constructed key must be bound to the SID value provided by this argument. If + * the wrappedKeyData does not contain such a tag and value, this argument must be + * ignored. + * + * @param biometricSid specifies the biometric secure ID (SID) of the user that owns the key + * being installed. If the authorization list in wrappedKeyData contains a + * Tag::USER_SECURE_ID with a value that has the HardwareAuthenticatorType::FINGERPRINT + * bit set, the constructed key must be bound to the SID value provided by this argument. + * If the wrappedKeyData does not contain such a tag and value, this argument must be + * ignored. + * + * @return The result of key creation. See KeyCreationResult.aidl. + */ + KeyCreationResult importWrappedKey(in byte[] wrappedKeyData, in byte[] wrappingKeyBlob, + in byte[] maskingKey, in KeyParameter[] unwrappingParams, in long passwordSid, + in long biometricSid); + + /** + * Upgrades an old key blob. Keys can become "old" in two ways: IKeyMintDevice can be + * upgraded to a new version with an incompatible key blob format, or the system can be updated + * to invalidate the OS version (OS_VERSION tag), system patch level (OS_PATCHLEVEL tag), + * vendor patch level (VENDOR_PATCH_LEVEL tag), boot patch level (BOOT_PATCH_LEVEL tag) or + * other, implementation-defined patch level (keyMint implementers are encouraged to extend + * this HAL with a minor version extension to define validatable patch levels for other + * images; tags must be defined in the implementer's namespace, starting at 10000). In either + * case, attempts to use an old key blob with begin() must result in IKeyMintDevice returning + * ErrorCode::KEY_REQUIRES_UPGRADE. The caller must use this method to upgrade the key blob. + * + * The upgradeKey method must examine each version or patch level associated with the key. If + * any one of them is higher than the corresponding current device value upgradeKey() must + * return ErrorCode::INVALID_ARGUMENT. There is one exception: it is always permissible to + * "downgrade" from any OS_VERSION number to OS_VERSION 0. For example, if the key has + * OS_VERSION 080001, it is permisible to upgrade the key if the current system version is + * 080100, because the new version is larger, or if the current system version is 0, because + * upgrades to 0 are always allowed. If the system version were 080000, however, keyMint must + * return ErrorCode::INVALID_ARGUMENT because that value is smaller than 080001. Values other + * than OS_VERSION must never be downgraded. + * + * Note that Keymaster versions 2 and 3 required that the system and boot images have the same + * patch level and OS version. This requirement is relaxed for 4.0::IKeymasterDevice and + * IKeyMintDevice, and the OS version in the boot image footer is no longer used. + * + * @param keyBlobToUpgrade The opaque descriptor returned by generateKey() or importKey(). + * + * @param upgradeParams A parameter list containing any parameters needed to complete the + * upgrade, including Tag::APPLICATION_ID and Tag::APPLICATION_DATA. + * + * @return A new key blob that references the same key as keyBlobToUpgrade, but is in the new + * format, or has the new version data. + */ + byte[] upgradeKey(in byte[] keyBlobToUpgrade, in KeyParameter[] upgradeParams); + + /** + * Deletes the key, or key pair, associated with the key blob. Calling this function on + * a key with Tag::ROLLBACK_RESISTANCE in its hardware-enforced authorization list must + * render the key permanently unusable. Keys without Tag::ROLLBACK_RESISTANCE may or + * may not be rendered unusable. + * + * @param keyBlob The opaque descriptor returned by generateKey() or importKey(); + * + * @return error See the ErrorCode enum. + */ + void deleteKey(in byte[] keyBlob); + + /** + * Deletes all keys in the hardware keystore. Used when keystore is reset completely. + * + * For StrongBox KeyMint: After this function is called all keys created previously must be + * rendered permanently unusable. + * + * For TEE KeyMint: After this function is called all keys with Tag::ROLLBACK_RESISTANCE in + * their hardware-enforced authorization lists must be rendered permanently unusable. Keys + * without Tag::ROLLBACK_RESISTANCE may or may not be rendered unusable. + */ + void deleteAllKeys(); + + /** + * Destroys knowledge of the device's ids. This prevents all device id attestation in the + * future. The destruction must be permanent so that not even a factory reset will restore the + * device ids. + * + * Device id attestation may be provided only if this method is fully implemented, allowing the + * user to permanently disable device id attestation. If this cannot be guaranteed, the device + * must never attest any device ids. + * + * This is a NOP if device id attestation is not supported. + */ + void destroyAttestationIds(); + + /** + * Begins a cryptographic operation using the specified key. If all is well, begin() must + * return ErrorCode::OK and create an IKeyMintOperation handle which will be used to perform + * the cryptographic operation. + * + * It is critical that each successful call to begin() be paired with a subsequent call to + * finish() or abort() on the resulting IKeyMintOperation, to allow the IKeyMintDevice + * implementation to clean up any internal operation state. The caller's failure to do this may + * leak internal state space or other internal resources and may eventually cause begin() to + * return ErrorCode::TOO_MANY_OPERATIONS when it runs out of space for operations. Any result + * other than ErrorCode::OK from begin() will not return an IKeyMintOperation (in which case + * calling finish() or abort() is neither possible nor necessary). IKeyMintDevice + * implementations must support 32 concurrent operations. + * + * If Tag::APPLICATION_ID or Tag::APPLICATION_DATA were specified during key generation or + * import, calls to begin must include those tags with the originally-specified values in the + * params argument to this method. If not, begin() must return ErrorCode::INVALID_KEY_BLOB. + * + * == Authorization Enforcement == + * + * The following key authorization parameters must be enforced by the IKeyMintDevice secure + * environment if the tags were returned in the "hardwareEnforced" list in the + * KeyCharacteristics. + * + * -- All Key Types -- + * + * The tags in this section apply to all key types. See below for additional key type-specific + * tags. + * + * o Tag::PURPOSE: The purpose specified in the begin() call must match one of the purposes in + * the key authorizations. If the specified purpose does not match, begin() must return + * ErrorCode::UNSUPPORTED_PURPOSE. + * + * o Tag::ACTIVE_DATETIME can only be enforced if a trusted UTC time source is available. If + * the current date and time is prior to the tag value, begin() must return + * ErrorCode::KEY_NOT_YET_VALID. + * + * o Tag::ORIGINATION_EXPIRE_DATETIME can only be enforced if a trusted UTC time source is + * available. If the current date and time is later than the tag value and the purpose is + * KeyPurpose::ENCRYPT or KeyPurpose::SIGN, begin() must return ErrorCode::KEY_EXPIRED. + * + * o Tag::USAGE_EXPIRE_DATETIME can only be enforced if a trusted UTC time source is + * available. If the current date and time is later than the tag value and the purpose is + * KeyPurpose::DECRYPT or KeyPurpose::VERIFY, begin() must return ErrorCode::KEY_EXPIRED. + * + * o Tag::MAX_USES_PER_BOOT must be compared against a secure counter that tracks the uses of + * the key since boot time. If the count of previous uses exceeds the tag value, begin() must + * return ErrorCode::KEY_MAX_OPS_EXCEEDED. + * + * o Tag::USER_SECURE_ID must be enforced by this method if and only if the key also has + * Tag::AUTH_TIMEOUT (if it does not have Tag::AUTH_TIMEOUT, the Tag::USER_SECURE_ID + * requirement must be enforced by updateAad(), update() and finish()). If the key has both, + * then this method must receive a non-empty HardwareAuthToken in the authToken argument. For + * the auth token to be valid, all of the following have to be true: + * + * o The HMAC field must validate correctly. + * + * o At least one of the Tag::USER_SECURE_ID values from the key must match at least one of + * the secure ID values in the token. + * + * o The key must have a Tag::USER_AUTH_TYPE that matches the auth type in the token. + * + * o If the device has a source of secure time, then the timestamp in the auth token plus the + * value of the Tag::AUTH_TIMEOUT must be greater than the current secure timestamp (which + * is a monotonic timer counting milliseconds since boot). + * + * o If the device does not have a source of secure time, then the timestamp check should be + * performed on the first update(), updateAad() or finish() invocation for the operation, + * using the timeStampToken parameter provided on the invocation to indicate the current + * timestamp. It may optionally also be performed on subsequent update() / updateAad() / + * finish() invocations. + * + * If any of these conditions are not met, begin() must return + * ErrorCode::KEY_USER_NOT_AUTHENTICATED. + * + * o Tag::CALLER_NONCE allows the caller to specify a nonce or initialization vector (IV). If + * the key doesn't have this tag, but the caller provided Tag::NONCE to this method, + * ErrorCode::CALLER_NONCE_PROHIBITED must be returned. + * + * o Tag::BOOTLOADER_ONLY specifies that only the bootloader may use the key. If this method is + * called with a bootloader-only key after the bootloader has finished executing, it must + * return ErrorCode::INVALID_KEY_BLOB. The mechanism for notifying the IKeyMintDevice that + * the bootloader has finished executing is implementation-defined. + * + * -- RSA Keys -- + * + * All RSA key operations must specify exactly one padding mode in params. If unspecified or + * specified more than once, the begin() must return ErrorCode::UNSUPPORTED_PADDING_MODE. + * + * RSA signing operations need a digest, as do RSA encryption and decryption operations with + * OAEP padding mode. For those cases, the caller must specify exactly one digest in params. + * If unspecified or specified more than once, begin() must return + * ErrorCode::UNSUPPORTED_DIGEST. + * + * Private key operations (KeyPurpose::DECRYPT and KeyPurpose::SIGN) need authorization of + * digest and padding, which means that the key authorizations need to contain the specified + * values. If not, begin() must return ErrorCode::INCOMPATIBLE_DIGEST or + * ErrorCode::INCOMPATIBLE_PADDING_MODE, as appropriate. + * + * With the exception of PaddingMode::NONE, all RSA padding modes are applicable only to certain + * purposes. Specifically, PaddingMode::RSA_PKCS1_1_5_SIGN and PaddingMode::RSA_PSS only + * support signing, while PaddingMode::RSA_PKCS1_1_5_ENCRYPT and PaddingMode::RSA_OAEP only + * support encryption and decryption. begin() must return ErrorCode::UNSUPPORTED_PADDING_MODE + * if the specified mode does not support the specified purpose. + * + * There are some important interactions between padding modes and digests: + * + * o PaddingMode::NONE indicates that a "raw" RSA operation is performed. If signing, + * Digest::NONE is specified for the digest. No digest is necessary for unpadded encryption + * or decryption. + * + * o PaddingMode::RSA_PKCS1_1_5_SIGN padding requires a digest. The digest may be Digest::NONE, + * in which case the KeyMint implementation cannot build a proper PKCS#1 v1.5 signature + * structure, because it cannot add the DigestInfo structure. Instead, the IKeyMintDevice + * must construct 0x00 || 0x01 || PS || 0x00 || M, where M is the provided message and PS is a + * random padding string at least eight bytes in length. The size of the RSA key has to be at + * least 11 bytes larger than the message, otherwise finish() must return + * ErrorCode::INVALID_INPUT_LENGTH. + * + * o PaddingMode::RSA_PKCS1_1_1_5_ENCRYPT padding does not require a digest. + * + * o PaddingMode::RSA_PSS padding requires a digest, which must match one of the digest values + * in the key authorizations, and which may not be Digest::NONE. begin() must return + * ErrorCode::INCOMPATIBLE_DIGEST if this is not the case. In addition, the size of the RSA + * key must be at least (D + S + 9) bits, where D is the size of the digest (in bits) and + * S is the size of the salt (in bits). The salt size S must equal D, so the RSA key must + * be at least (2*D + 9) bits. Otherwise begin() must return ErrorCode::INCOMPATIBLE_DIGEST. + * + * o PaddingMode::RSA_OAEP padding requires a digest, which must match one of the digest values + * in the key authorizations, and which may not be Digest::NONE. begin() must return + * ErrorCode::INCOMPATIBLE_DIGEST if this is not the case. RSA_OAEP padding also requires an + * MGF1 digest, specified with Tag::RSA_OAEP_MGF_DIGEST, which must match one of the MGF1 + * padding values in the key authorizations and which may not be Digest::NONE. begin() must + * return ErrorCode::INCOMPATIBLE_MGF_DIGEST if this is not the case. The OAEP mask generation + * function must be MGF1. + * + * -- EC Keys -- + * + * Private key operations (KeyPurpose::SIGN) need authorization of digest, which means that the + * key authorizations must contain the specified values. If not, begin() must return + * ErrorCode::INCOMPATIBLE_DIGEST. + * + * -- AES Keys -- + * + * AES key operations must specify exactly one block mode (Tag::BLOCK_MODE) and one padding mode + * (Tag::PADDING) in params. If either value is unspecified or specified more than once, + * begin() must return ErrorCode::UNSUPPORTED_BLOCK_MODE or + * ErrorCode::UNSUPPORTED_PADDING_MODE. The specified modes must be authorized by the key, + * otherwise begin() must return ErrorCode::INCOMPATIBLE_BLOCK_MODE or + * ErrorCode::INCOMPATIBLE_PADDING_MODE. + * + * If the block mode is BlockMode::GCM, params must specify Tag::MAC_LENGTH, and the specified + * value must be a multiple of 8 that is not greater than 128 or less than the value of + * Tag::MIN_MAC_LENGTH in the key authorizations. For MAC lengths greater than 128 or + * non-multiples of 8, begin() must return ErrorCode::UNSUPPORTED_MAC_LENGTH. For values less + * than the key's minimum length, begin() must return ErrorCode::INVALID_MAC_LENGTH. + * + * If the block mode is BlockMode::GCM or BlockMode::CTR, the specified padding mode must be + * PaddingMode::NONE. For BlockMode::ECB or BlockMode::CBC, the mode may be PaddingMode::NONE + * or PaddingMode::PKCS7. If the padding mode doesn't meet these conditions, begin() must + * return ErrorCode::INCOMPATIBLE_PADDING_MODE. + * + * If the block mode is BlockMode::CBC, BlockMode::CTR, or BlockMode::GCM, an initialization + * vector or nonce is required. In most cases, callers shouldn't provide an IV or nonce and the + * IKeyMintDevice implementation must generate a random IV or nonce and return it via Tag::NONCE + * in outParams. CBC and CTR IVs are 16 bytes. GCM nonces are 12 bytes. If the key + * authorizations contain Tag::CALLER_NONCE, then the caller may provide an IV/nonce with + * Tag::NONCE in params, which must be of the correct size (if not, return + * ErrorCode::INVALID_NONCE). If a nonce is provided when Tag::CALLER_NONCE is not authorized, + * begin() must return ErrorCode::CALLER_NONCE_PROHIBITED. If a nonce is not provided when + * Tag::CALLER_NONCE is authorized, IKeyMintDevice must generate a random IV/nonce. + * + * -- 3DES Keys -- + * + * 3DES key operations must specify exactly one block mode (Tag::BLOCK_MODE) and one padding + * mode (Tag::PADDING) in params. If either value is unspecified or specified more than once, + * begin() must return ErrorCode::UNSUPPORTED_BLOCK_MODE or + * ErrorCode::UNSUPPORTED_PADDING_MODE. The specified modes must be authorized by the key, + * otherwise begin() must return ErrorCode::INCOMPATIBLE_BLOCK_MODE or + * ErrorCode::INCOMPATIBLE_PADDING_MODE. + * + * If the block mode is BlockMode::CBC, an initialization vector or nonce is required. In most + * cases, callers shouldn't provide an IV or nonce and the IKeyMintDevice implementation must + * generate a random IV or nonce and return it via Tag::NONCE in outParams. CBC IVs are 8 + * bytes. If the key authorizations contain Tag::CALLER_NONCE, then the caller may provide an + * IV/nonce with Tag::NONCE in params, which must be of the correct size (if not, return + * ErrorCode::INVALID_NONCE). If a nonce is provided when Tag::CALLER_NONCE is not authorized, + * begin() must return ErrorCode::CALLER_NONCE_PROHIBITED. If a nonce is not provided when + * Tag::CALLER_NONCE is authorized, IKeyMintDevice must generate a random IV/nonce. + * + * + * -- HMAC keys -- + * + * HMAC key operations must specify Tag::MAC_LENGTH in params. The specified value must be a + * multiple of 8 that is not greater than the digest length or less than the value of + * Tag::MIN_MAC_LENGTH in the key authorizations. For MAC lengths greater than the digest + * length or non-multiples of 8, begin() must return ErrorCode::UNSUPPORTED_MAC_LENGTH. For + * values less than the key's minimum length, begin() must return ErrorCode::INVALID_MAC_LENGTH. + * + * @param purpose The purpose of the operation, one of KeyPurpose::ENCRYPT, KeyPurpose::DECRYPT, + * KeyPurpose::SIGN, KeyPurpose::VERIFY, or KeyPurpose::AGREE_KEY. Note that for AEAD + * modes, encryption and decryption imply signing and verification, respectively, but + * must be specified as KeyPurpose::ENCRYPT and KeyPurpose::DECRYPT. + * + * @param keyBlob The opaque key descriptor returned by generateKey() or importKey(). The key + * must have a purpose compatible with purpose and all of its usage requirements must be + * satisfied, or begin() must return an appropriate error code (see above). + * + * @param params Additional parameters for the operation. If Tag::APPLICATION_ID or + * Tag::APPLICATION_DATA were provided during generation, they must be provided here, or + * the operation must fail with ErrorCode::INVALID_KEY_BLOB. For operations that require + * a nonce or IV, on keys that were generated with Tag::CALLER_NONCE, params may + * contain a tag Tag::NONCE. If Tag::NONCE is provided for a key without + * Tag:CALLER_NONCE, ErrorCode::CALLER_NONCE_PROHIBITED must be returned. + * + * @param authToken Authentication token. + * + * @return BeginResult as output, which contains the challenge, KeyParameters which haves + * additional data from the operation initialization, notably to return the IV or nonce + * from operations that generate an IV or nonce, and IKeyMintOperation object pointer + * which is used to perform updateAad(), update(), finish() or abort() operations. + */ + BeginResult begin(in KeyPurpose purpose, in byte[] keyBlob, in KeyParameter[] params, + in @nullable HardwareAuthToken authToken); + + /** + * This method is deprecated and has never been used. Implementations should return + * ErrorCode::UNIMPLEMENTED. + * + * This method was originally intended to be used to notify KeyMint that the device is now + * locked, and keys with the UNLOCKED_DEVICE_REQUIRED tag should no longer be usable until a + * later valid HardwareAuthToken is presented. However, Android has never called this method + * and it cannot start doing so, because KeyMint's enforcement of UNLOCKED_DEVICE_REQUIRED did + * not provide the correct semantics and therefore could never be enabled. Specifically, the + * following issues existed with the design of KeyMint's enforcement of + * UNLOCKED_DEVICE_REQUIRED: + * + * o It assumed a global device lock state only.  Android actually has a separate lock state for + * each user. See the javadoc for KeyguardManager#isDeviceLocked(). + * o It assumed that unlocking the device involves a successful user authentication that + * generates a HardwareAuthToken. This is not necessarily the case, since Android supports + * weaker unlock methods including class 1 and 2 biometrics and trust agents. These unlock + * methods do not generate a HardwareAuthToken or interact with KeyMint in any way. Also, + * UNLOCKED_DEVICE_REQUIRED must work even for users who do not have a secure lock screen. + * o It would have made UNLOCKED_DEVICE_REQUIRED incompatible with requiring user + * authentication in some cases. These two key protections can each require a different + * HardwareAuthToken, but KeyMint only supports one HardwareAuthToken per operation. + * o It would have provided no security benefit over Keystore's enforcement of + * UNLOCKED_DEVICE_REQUIRED. This is because since Android 12, Keystore enforces + * UNLOCKED_DEVICE_REQUIRED not just logically, but it also cryptographically by + * superencrypting all such keys and wiping or re-encrypting the superencryption key when the + * device is locked (whenever possible). KeyMint is still used to support biometric unlocks, + * but this mechanism does not use KeyMint's direct enforcement of UNLOCKED_DEVICE_REQUIRED. + * + * Therefore, this method is not useful, and there is no reason for it be called. + * Implementations should return ErrorCode::UNIMPLEMENTED and should not include + * UNLOCKED_DEVICE_REQUIRED in the list of hardware-enforced key parameters. + * + * @param passwordOnly N/A due to the deprecation + * @param timestampToken N/A due to the deprecation + * @deprecated Method has never been used due to design limitations + */ + void deviceLocked(in boolean passwordOnly, in @nullable TimeStampToken timestampToken); + + /** + * Called by client to notify the IKeyMintDevice that the device has left the early boot + * state, and that keys with the EARLY_BOOT_ONLY tag may no longer be used. All attempts to use + * an EARLY_BOOT_ONLY key after this method is called must fail with Error::EARLY_BOOT_ENDED. + */ + void earlyBootEnded(); + + /** + * Called by the client to get a wrapped per-boot ephemeral key from a wrapped storage key. + * Clients will then use the returned per-boot ephemeral key in place of the wrapped storage + * key. Whenever the hardware is presented with a per-boot ephemeral key for an operation, it + * must use the storage key associated with that ephemeral key to perform the requested + * operation. + * + * Implementations should return ErrorCode::UNIMPLEMENTED if they don't support wrapped storage + * keys. + * + * Implementations should return ErrorCode::INVALID_ARGUMENT (as a ServiceSpecificException) + * if the input key blob doesn't represent a valid long-lived wrapped storage key. + * + * @param storageKeyBlob is the wrapped storage key for which the client wants a per-boot + * ephemeral key + * + * @return a buffer containing the per-boot ephemeral keyblob that should henceforth be used in + * place of the input storageKeyBlob + */ + byte[] convertStorageKeyToEphemeral(in byte[] storageKeyBlob); + + /** + * Returns KeyMint-enforced parameters associated with the provided key. The returned tags are + * a subset of KeyCharacteristics found in the KeyCreationResult returned by generateKey(), + * importKey(), or importWrappedKey(). The returned value is a subset, as it does not include + * any Keystore-enforced parameters. + * + * @param keyBlob The opaque descriptor returned by generateKey, importKey or importWrappedKey. + * + * @param appId An opaque byte string identifying the client. This value must match the + * Tag::APPLICATION_ID data provided during key generation/import. Without the correct + * value, it must be computationally infeasible for the secure hardware to obtain the + * key material. + * + * @param appData An opaque byte string provided by the application. This value must match the + * Tag::APPLICATION_DATA data provided during key generation/import. Without the + * correct value, it must be computationally infeasible for the secure hardware to + * obtain the key material. + * + * @return Characteristics of the generated key. See KeyCreationResult for details. + */ + KeyCharacteristics[] getKeyCharacteristics( + in byte[] keyBlob, in byte[] appId, in byte[] appData); + + /** + * Returns a 16-byte random challenge nonce, used to prove freshness when exchanging root of + * trust data. + * + * This method may only be implemented by StrongBox KeyMint. TEE KeyMint implementations must + * return ErrorCode::UNIMPLEMENTED. StrongBox KeyMint implementations MAY return UNIMPLEMENTED, + * to indicate that they have an alternative mechanism for getting the data. If the StrongBox + * implementation returns UNIMPLEMENTED, the client should not call `getRootofTrust()` or + * `sendRootOfTrust()`. + */ + byte[16] getRootOfTrustChallenge(); + + /** + * Returns the TEE KeyMint Root of Trust data. + * + * This method is required for TEE KeyMint. StrongBox KeyMint implementations MUST return + * ErrorCode::UNIMPLEMENTED. + * + * The returned data is an encoded COSE_Mac0 structure, denoted MacedRootOfTrust in the + * following CDDL schema. Note that K_mac is the shared HMAC key used for auth tokens, etc.: + * + * MacedRootOfTrust = #6.17 [ ; COSE_Mac0 (tagged) + * protected: bstr .cbor { + * 1 : 5, ; Algorithm : HMAC-256 + * }, + * unprotected : {}, + * payload : bstr .cbor RootOfTrust, + * tag : bstr HMAC-256(K_mac, MAC_structure) + * ] + * + * MAC_structure = [ + * context : "MAC0", + * protected : bstr .cbor { + * 1 : 5, ; Algorithm : HMAC-256 + * }, + * external_aad : bstr .size 16 ; Value of challenge argument + * payload : bstr .cbor RootOfTrust, + * ] + * + * RootOfTrust = #6.40001 [ ; Tag 40001 indicates RoT v1. + * verifiedBootKey : bstr .size 32, + * deviceLocked : bool, + * verifiedBootState : &VerifiedBootState, + * verifiedBootHash : bstr .size 32, + * bootPatchLevel : int, ; See Tag::BOOT_PATCHLEVEL + * ] + * + * VerifiedBootState = ( + * Verified : 0, + * SelfSigned : 1, + * Unverified : 2, + * Failed : 3 + * ) + */ + byte[] getRootOfTrust(in byte[16] challenge); + + /** + * Delivers the TEE KeyMint Root of Trust data to StrongBox KeyMint. See `getRootOfTrust()` + * above for specification of the data format and cryptographic security structure. + * + * The implementation must verify the MAC on the RootOfTrust data. If it is valid, and if this + * is the first time since reboot that StrongBox KeyMint has received this data, it must store + * the RoT data for use in key attestation requests, then return ErrorCode::ERROR_OK. + * + * If the MAC on the Root of Trust data and challenge is incorrect, the implementation must + * return ErrorCode::VERIFICATION_FAILED. + * + * If the RootOfTrust data has already been received since the last boot, the implementation + * must validate the data and return ErrorCode::VERIFICATION_FAILED or ErrorCode::ERROR_OK + * according to the result, but must not store the data for use in key attestation requests, + * even if verification succeeds. On success, the challenge is invalidated and a new challenge + * must be requested before the RootOfTrust data may be sent again. + * + * This method is optional for StrongBox KeyMint, which MUST return ErrorCode::UNIMPLEMENTED if + * not implemented. TEE KeyMint implementations must return ErrorCode::UNIMPLEMENTED. + */ + void sendRootOfTrust(in byte[] rootOfTrust); + + /** + * Called by Android to deliver additional attestation information to the IKeyMintDevice. + * + * IKeyMintDevice must ignore KeyParameters with tags not included in the following list: + * + * o Tag::MODULE_HASH: holds a hash that must be included in attestations in the moduleHash + * field of the software enforced authorization list. + * + * @return error ErrorCode::MODULE_HASH_ALREADY_SET if this is not the first time + * setAdditionalAttestationInfo is called with Tag::MODULE_HASH, and the associated + * KeyParamValue of the current call doesn't match the KeyParamValue of the first call. + */ + void setAdditionalAttestationInfo(in KeyParameter[] info); +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl b/libs/rust/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl new file mode 100644 index 0000000..a4fab55 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.HardwareAuthToken; +import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.secureclock.TimeStampToken; + +/** @hide */ +@VintfStability +@SensitiveData +interface IKeyMintOperation { + /** + * Provides additional authentication data (AAD) to a cryptographic operation begun with + * begin(), provided in the input argument. This method only applies to AEAD modes. This + * method may be called multiple times, supplying the AAD in chunks, but may not be called after + * update() is called. If updateAad() is called after update(), it must return + * ErrorCode::INVALID_TAG. + * + * If the operation is in an invalid state (was aborted or had an error) update() must return + * ErrorCode::INVALID_OPERATION_HANDLE. + * + * If this method returns an error code other than ErrorCode::OK, the operation is aborted and + * the operation handle must be invalidated. Any future use of this object must return + * ErrorCode::INVALID_OPERATION_HANDLE. + * + * == Authorization Enforcement == + * + * See the Authorization Enforcement section for the update() method. + * + * + * For GCM encryption, the AEAD tag must be appended to the ciphertext by finish(). During + * decryption, the last Tag::MAC_LENGTH bytes of the data provided to the last update call must + * be the AEAD tag. Since a given invocation of update cannot know if it's the last invocation, + * it must process all but the tag length and buffer the possible tag data for processing during + * finish(). + * + * @param input Additional Authentication Data to be processed. + * + * @param authToken Authentication token, if provided. + * + * @param timeStampToken timestamp token, certifies the freshness of an auth token in case + * the security domain of this KeyMint instance has a different clock than the + * authenticator issuing the auth token. + * + * @return error Returns ErrorCode encountered in keymint as service specific errors. See the + * ErrorCode enum in ErrorCode.aidl. + */ + void updateAad(in byte[] input, in @nullable HardwareAuthToken authToken, + in @nullable TimeStampToken timeStampToken); + + /** + * Provides data to, and possibly receives output from, an ongoing cryptographic operation begun + * with begin(). + * + * If operation is in an invalid state (was aborted or had an error) update() must return + * ErrorCode::INVALID_OPERATION_HANDLE. + * + * Implementations may choose how much data to return as a result of the update. This is + * only relevant for encryption and decryption operations, because signing returns no data + * until finish. It is recommended to return data as early as possible, rather than buffer it. + * + * If this method returns an error code other than ErrorCode::OK, the operation is aborted and + * the operation handle must be invalidated. Any future use of the handle, with this method, + * finish, or abort, must return ErrorCode::INVALID_OPERATION_HANDLE. + * + * == Authorization Enforcement == + * + * Key authorization enforcement is performed primarily in IKeyMintDevice::begin(). There are + * two exceptions to this: + * + * 1) Key with USER_SECURE_IDs but no AUTH_TIMEOUT + * + * 2) Key with USER_SECURE_IDs and AUTH_TIMEOUT, but the device does not support secure time. + * + * The first exception is the case where the key: + * + * o Has one or more Tag::USER_SECURE_IDs, and + * + * o Does not have a Tag::AUTH_TIMEOUT + * + * In this case, the key requires an authorization per operation, and update() / updateAad() / + * finish() methods must receive a non-null and valid HardwareAuthToken. For the auth token to + * be valid, all of the following has to be true: + * + * o The HMAC field must validate correctly. + * + * o At least one of the Tag::USER_SECURE_ID values from the key must match at least one of + * the secure ID values in the token. + * + * o The key must have a Tag::USER_AUTH_TYPE that matches the auth type in the token. + * + * o The challenge field in the auth token must contain the challenge value contained in the + * BeginResult returned from IKeyMintDevice::begin(). + * + * If any of these conditions are not met, the method must return + * ErrorCode::KEY_USER_NOT_AUTHENTICATED. + * + * The caller must provide the auth token on every call to update(), updateAad() and finish(). + * + * + * The second exception is the case where the key: + * + * o Has one or more Tag::USER_SECURE_IDs, and + * + * o Has a Tag::AUTH_TIMEOUT value, but the device does not have a source of secure time (as + * indicated by the KeyMintHardwareInfo.timestampTokenRequired field). + * + * In this case, the key requires an per-operation authorization on the first call to update(), + * updateAad() or finish() for the operation, using the provided timeStampToken as a source of + * secure time. For this timeStampToken to be valid, all of the following has to be true: + * + * o The HMAC field must validate correctly. + * + * o The challenge field in the timestamp token must contain the challenge value contained in + * the BeginResult returned from IKeyMintDevice::begin(). + * + * The resulting secure time value is then used to authenticate the HardwareAuthToken. For the + * auth token to be valid, all of the following has to be true: + * + * o The HMAC field must validate correctly. + * + * o At least one of the Tag::USER_SECURE_ID values from the key must match at least one of + * the secure ID values in the token. + * + * o The key must have a Tag::USER_AUTH_TYPE that matches the auth type in the token. + * + * o The timestamp in the auth token plus the value of the Tag::AUTH_TIMEOUT must be greater + * than the provided secure timestamp. + + * If any of these conditions are not met, the method must return + * ErrorCode::KEY_USER_NOT_AUTHENTICATED. + * + * + * -- RSA keys -- + * + * For signing operations with Digest::NONE, this method must accept the entire block to be + * signed in a single update. It may not consume only a portion of the block in these cases. + * However, the caller may choose to provide the data in multiple updates, and update() must + * accept the data this way as well. If the caller provides more data to sign than can be used + * (length of data exceeds RSA key size), update() must return ErrorCode::INVALID_INPUT_LENGTH. + * + * -- ECDSA keys -- + * + * For signing operations with Digest::NONE, this method must accept the entire block to be + * signed in a single update. This method may not consume only a portion of the block. + * However, the caller may choose to provide the data in multiple updates and update() must + * accept the data this way as well. If the caller provides more data to sign than can be used, + * the data is silently truncated. (This differs from the handling of excess data provided in + * similar RSA operations. The reason for this is compatibility with legacy clients.) + * + * -- AES keys -- + * + * For GCM encryption, the AEAD tag must be appended to the ciphertext by finish(). During + * decryption, the last Tag::MAC_LENGTH bytes of the data provided to the last update call must + * be the AEAD tag. Since a given invocation of update cannot know if it's the last invocation, + * it must process all but the tag length and buffer the possible tag data for processing during + * finish(). + * + * @param input Data to be processed. update() must consume all input data. + * + * @param authToken Authentication token. Can be nullable if not provided. + * + * @param timeStampToken certifies the freshness of an auth token in case the security domain of + * this KeyMint instance has a different clock than the authenticator issuing the auth + * token. + * + * @return error Returns ErrorCode encountered in keymint as service specific errors. See the + * ErrorCode enum in ErrorCode.aidl. + * + * @return byte[] The output data, if any. + */ + byte[] update(in byte[] input, in @nullable HardwareAuthToken authToken, + in @nullable TimeStampToken timeStampToken); + + /** + * Finalizes a cryptographic operation begun with begin() and invalidates the operation. + * + * This method is the last one called in an operation, so all processed data must be returned. + * + * Whether it completes successfully or returns an error, this method finalizes the operation. + * Any future use of the operation, with finish(), update(), or abort(), must return + * ErrorCode::INVALID_OPERATION_HANDLE. + * + * Signing operations return the signature as the output. + * + * == Authorization enforcement == + * + * Key authorization enforcement is performed primarily in begin(). The exceptions are + * authorization per operation keys and confirmation-required keys. + * + * Authorization per operation keys must be authorized as described for the update() method. + * + * Confirmation-required keys are keys that were generated with + * Tag::TRUSTED_CONFIRMATION_REQUIRED. For these keys, when doing a signing operation the + * caller must pass a KeyParameter Tag::CONFIRMATION_TOKEN to finish(). Implementations must + * check the confirmation token by computing the 32-byte HMAC-SHA256 over all of the + * to-be-signed data, prefixed with the 18-byte UTF-8 encoded string "confirmation token". If + * the computed value does not match the Tag::CONFIRMATION_TOKEN parameter, finish() must not + * produce a signature and must return ErrorCode::NO_USER_CONFIRMATION. + * + * -- RSA keys -- + * + * Some additional requirements, depending on the padding mode: + * + * o PaddingMode::NONE. For unpadded signing and encryption operations, if the provided data is + * shorter than the key, the data must be zero-padded on the left before signing/encryption. + * If the data is the same length as the key, but numerically larger, finish() must return + * ErrorCode::INVALID_ARGUMENT. For decryption operations, the data must be exactly as long + * as the key. Otherwise, return ErrorCode::INVALID_INPUT_LENGTH. + * + * o PaddingMode::RSA_PSS. For PSS-padded signature operations, the PSS salt length must match + * the size of the PSS digest selected. The digest specified with Tag::DIGEST in params + * on begin() must be used as the PSS digest algorithm, MGF1 must be used as the mask + * generation function and the digest specified with Tag:DIGEST in params on begin() must also + * be used as the MGF1 digest algorithm. + * + * -- ECDSA keys -- + * + * If the data provided for undigested signing is too long, truncate it. + * + * -- AES keys -- + * + * Some additional conditions, depending on block mode: + * + * o BlockMode::ECB or BlockMode::CBC. If padding is PaddingMode::NONE and the data length is + * not a multiple of the AES block size, finish() must return + * ErrorCode::INVALID_INPUT_LENGTH. If padding is PaddingMode::PKCS7, pad the data per the + * PKCS#7 specification, including adding an additional padding block if the data is a + * multiple of the block length. If padding is PaddingMode::PKCS7 and decryption does not + * result in valid padding, return ErrorCode::INVALID_ARGUMENT. + * + * o BlockMode::GCM. During encryption, after processing all plaintext, compute the tag + * (Tag::MAC_LENGTH bytes) and append it to the returned ciphertext. During decryption, + * process the last Tag::MAC_LENGTH bytes as the tag. If tag verification fails, finish() + * must return ErrorCode::VERIFICATION_FAILED. + * + * @param input Data to be processed, per the parameters established in the call to begin(). + * finish() must consume all provided data or return ErrorCode::INVALID_INPUT_LENGTH. + * + * @param signature The signature to be verified if the purpose specified in the begin() call + * was KeyPurpose::VERIFY. + * + * @param authToken Authentication token. Can be nullable if not provided. + * + * @param timestampToken certifies the freshness of an auth token in case the security domain of + * this KeyMint instance has a different clock than the authenticator issuing the auth + * token. + * + * @param confirmationToken is the confirmation token required by keys with + * Tag::TRUSTED_CONFIRMATION_REQUIRED. + * + * @return The output data, if any. + */ + byte[] finish(in @nullable byte[] input, in @nullable byte[] signature, + in @nullable HardwareAuthToken authToken, + in @nullable TimeStampToken timestampToken, + in @nullable byte[] confirmationToken); + + /** + * Aborts a cryptographic operation begun with IKeyMintDevice::begin(), freeing all internal + * resources. If an operation was finalized, calling updateAad, update, finish, or abort yields + * ErrorCode::INVALID_OPERATION_HANDLE. An operation is finalized if finish or abort was called + * on it, or if updateAad or update returned an ErrorCode. + * + * @return error See the ErrorCode enum in ErrorCode.aidl. + */ + void abort(); +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl new file mode 100644 index 0000000..f0df048 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.SecurityLevel; + +/** + * KeyCharacteristics defines the attributes of a key that are enforced by KeyMint, and the security + * level (see SecurityLevel.aidl) of that enforcement. + * + * The `generateKey` `importKey` and `importWrappedKey` methods each return an array of + * KeyCharacteristics, specifying the security levels of enforcement and the authorizations + * enforced. Note that enforcement at a given security level means that the semantics of the tag + * and value are fully enforced. See the definition of individual tags for specifications of what + * must be enforced. + * @hide + */ +@VintfStability +parcelable KeyCharacteristics { + /** + * The security level enforcing this collection of key properties. + */ + SecurityLevel securityLevel = SecurityLevel.SOFTWARE; + + /** + * `authorizations` is a list of key properties that are enforced at this security level. + * A key can have different properties enforced by components of different security levels. + * For example, some properties are provided by the operating system, which has a + * different security level to the IKeyMintDevice. + * See the `keyCharacteristics` field in `KeyCreationResult` for more details. + */ + KeyParameter[] authorizations; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyCreationResult.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyCreationResult.aidl new file mode 100644 index 0000000..2d2f307 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyCreationResult.aidl @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.Certificate; +import android.hardware.security.keymint.KeyCharacteristics; + +/** + * This structure is returned when a new key is created with generateKey(), importKey() or + * importWrappedKey(). + * @hide + */ +@VintfStability +parcelable KeyCreationResult { + /** + * `keyBlob` is an descriptor of the generated/imported key key. + */ + byte[] keyBlob; + + /** + * `keyCharacteristics` is a description of the generated key in the form of authorization lists + * associated with security levels. The rules that IKeyMintDevice implementations must use for + * deciding whether a given tag from `keyParams` argument to the generation/import method should + * be returned in `keyCharacteristics` are: + * + * - If the semantics of the tag are fully enforced by the IKeyMintDevice, without any + * assistance from components running at other security levels, it should be included in an + * entry with the SecurityLevel of the IKeyMintDevice. + * - If the semantics of the tag are fully enforced, but with the assistance of components + * running at another SecurityLevel, it should be included in an entry with the minimum + * SecurityLevel of the involved components. For example if a StrongBox IKeyMintDevice relies + * on a TEE to validate biometric authentication, biometric authentication tags go in an entry + * with SecurityLevel::TRUSTED_ENVIRONMENT. + * - If the semantics are not enforced by KeyMint at all, SecurityLevel::KEYSTORE is used to + * indicate that Keystore should enforce. Note that in Keymaster (predecessor to KeyMint), + * these tags would have been in SecurityLevel::SOFTWARE. + */ + KeyCharacteristics[] keyCharacteristics; + + /** + * If the generated/imported key is an asymmetric key, `certificateChain` will contain a chain + * of one or more certificates. + * + * There are a few variations in what is contained in `certificateChain`, depending on whether + * the caller requested attestation, whether they provided an attestation key (via the + * `attestationKey` parameter of `generateKey()`, `importKey()` or `importWrappedKey()`), and in + * the non-attestation case, whether the key can self-sign. + * + * 1. Asymmetric key attestation with factory key. If Tag::ATTESTATION_CHALLENGE is provided + * and the `attestationKey` parameter on the generate/import call is null, and if the + * KeyMint implementation supports factory-provisioned attestation keys, the returned + * certificate chain must contain an attestation certificate signed with a factory- + * provisioned attestation key, and the full certificate chain for that factory-provisioned + * attestation key. Tag::ATTESTATION_APPLICATION_ID must also be provided when the + * ATTESTATION_CHALLENGE is provided, otherwise ATTESTATION_APPLICATION_ID_MISSING will be + * returned. KeyMint implementations are not required to support factory-provisioned + * attestation keys. If the KeyMint implementation does not support factory-provisioned + * keys, it must return ATTESTATION_KEYS_NOT_PROVISIONED. + * + * 2. Asymmetric key attestation with caller-provided key. If Tag::ATTESTATION_CHALLENGE is + * provided and the `attestationKey` parameter on the generate/import call is non-null and + * contains the key blob of a key with KeyPurpose::ATTEST_KEY, the returned certificate + * chain must contain only an attestation certificate signed with the specified key. The + * caller must know the certificate chain for the provided key. Tag:: + * ATTESTATION_APPLICATION_ID must also be provided when the ATTESTATION_CHALLENGE is + * provided, otherwise ATTESTATION_APPLICATION_ID_MISSING will be returned. + * + * 3. Asymmetric key non-attestation with signing key. If Tag::ATTESTATION_CHALLENGE is not + * provided and the generated/imported key has KeyPurpose::SIGN or KeyPurpose::ATTEST_KEY, + * then the returned certificate chain must contain only a single self-signed certificate + * with no attestation extension. Tag::ATTESTATION_APPLICATION_ID will be ignored if + * provided. + * + * 4. Asymmetric key non-attestation with non-signing key. If TAG::ATTESTATION_CHALLENGE is + * not provided and the generated/imported key does not have KeyPurpose::SIGN nor + * KeyPurpose::ATTEST_KEY, then the returned certificate chain must contain only a single + * certificate with an empty signature and no attestation extension. + * Tag::ATTESTATION_APPLICATION_ID will be ignored if provided. + * + * 5. Symmetric key. If the generated/imported key is symmetric, the certificate chain must + * return empty, any Tag::ATTESTATION_CHALLENGE or Tag::ATTESTATION_APPLICATION_ID inputs, + * if provided, are ignored. + * + * In all cases except the symmetric key, the contents of certificate chain must be DER-encoded + * X.509 certificates ordered such that each certificate is signed by the subsequent one, up to + * the root which must be self-signed (or contain a fake signature in the case of case 4 above). + * The first certificate in the chain signs the public key info of the newly-generated or + * newly-imported key pair. The first certificate must also satisfy some other requirements: + * + * o It must have the serial number provided in Tag::CERTIFICATE_SERIAL, or default to 1 if the + * tag is not provided. + * + * o It must have the subject provided in Tag::CERTIFICATE_SUBJECT, or default to CN="Android + * Keystore Key", if the tag is not provided. + * + * o It must contain the notBefore and notAfter date-times specified in + * Tag::CERTIFICATE_NOT_BEFORE and Tag::CERTIFICATE_NOT_AFTER, respectively. + * + * o It must contain a Key Usage extension with: + * + * - the digitalSignature bit set iff the attested key has KeyPurpose::SIGN, + * - the dataEncipherment bit set iff the attested key has KeyPurpose::DECRYPT, + * - the keyEncipherment bit set iff the attested key has KeyPurpose::WRAP_KEY, + * - the keyAgreement bit set iff the attested key has KeyPurpose::AGREE_KEY, and + * - the keyCertSignBit set iff the attested key has KeyPurpose::ATTEST_KEY. + * + * In the attestation cases (1 and 2 above), the first certificate must contain a + * KeyDescription attestation extension with OID 1.3.6.1.4.1.11129.2.1.17. + * + * The KeyDescription content is defined by the following ASN.1 schema, which is mostly a + * straightforward translation of the KeyMint tag/value parameter lists to ASN.1. + * + * KeyDescription ::= SEQUENCE { + * -- attestationVersion must be 400. + * attestationVersion INTEGER, + * -- attestationSecurityLevel is the SecurityLevel of the location where the attested + * -- key is stored. Must match keymintSecurityLevel. + * attestationSecurityLevel SecurityLevel, + * -- keyMintVersion must be 400. + * keyMintVersion INTEGER, + * -- keyMintSecurityLevel is the SecurityLevel of the IKeyMintDevice. Must match + * -- attestationSecurityLevel. + * keyMintSecurityLevel SecurityLevel, + * -- attestationChallenge contains Tag::ATTESTATION_CHALLENGE from attestParams. + * attestationChallenge OCTET_STRING, + * -- uniqueId is empty unless the key has Tag::INCLUDE_UNIQUE_ID. + * uniqueId OCTET_STRING, + * -- softwareEnforced contains the authorization tags enforced by the Android system. + * softwareEnforced AuthorizationList, + * -- hardwareEnforced contains the authorization tags enforced by a secure environment + * -- (TEE or StrongBox). + * hardwareEnforced AuthorizationList, + * } + * + * SecurityLevel ::= ENUMERATED { + * Software (0), + * TrustedEnvironment (1), + * StrongBox (2), + * } + * + * RootOfTrust ::= SEQUENCE { + * -- verifiedBootKey must contain a SHA-256 digest of the public key embedded in the + * -- "vbmeta" partition if the device's bootloader is locked, or 32 bytes of zeroes if the + * -- device's bootloader is unlocked. + * verifiedBootKey OCTET_STRING, + * deviceLocked BOOLEAN, + * verifiedBootState VerifiedBootState, + * -- verifiedBootHash must contain a SHA-256 digest of all binaries and components + * -- validated by Verified Boot. Updating any verified binary or component must cause this + * -- value to change. + * verifiedBootHash OCTET_STRING, + * } + * + * VerifiedBootState ::= ENUMERATED { + * Verified (0), + * SelfSigned (1), + * Unverified (2), + * Failed (3), + * } + * + * -- Modules contains version information for APEX modules. + * -- Note that the Modules information is DER-encoded before being hashed, which requires a + * -- specific ordering (lexicographic by encoded value) for the constituent Module entries. + * -- This ensures that the ordering of Module entries is predictable and that the resulting + * -- SHA-256 hash value is identical for the same set of modules. + * Modules ::= SET OF Module + * Module ::= SEQUENCE { + * packageName OCTET_STRING, + * version INTEGER, -- As determined at boot time + * } + * + * -- Note that the AuthorizationList SEQUENCE is also used in IKeyMintDevice::importWrappedKey + * -- as a way of describing the authorizations associated with a key that is being securely + * -- imported. As such, it includes the ability to describe tags that are only relevant for + * -- symmetric keys, and which will never appear in the attestation extension of an X.509 + * -- certificate that holds the public key part of an asymmetric keypair. Importing a wrapped + * -- key also allows the use of Tag::USER_SECURE_ID, which is never included in an attestation + * -- extension because it has no meaning off-device. + * + * AuthorizationList ::= SEQUENCE { + * purpose [1] EXPLICIT SET OF INTEGER OPTIONAL, + * algorithm [2] EXPLICIT INTEGER OPTIONAL, + * keySize [3] EXPLICIT INTEGER OPTIONAL, + * blockMode [4] EXPLICIT SET OF INTEGER OPTIONAL, -- Symmetric keys only + * digest [5] EXPLICIT SET OF INTEGER OPTIONAL, + * padding [6] EXPLICIT SET OF INTEGER OPTIONAL, + * callerNonce [7] EXPLICIT NULL OPTIONAL, -- Symmetric keys only + * minMacLength [8] EXPLICIT INTEGER OPTIONAL, -- Symmetric keys only + * ecCurve [10] EXPLICIT INTEGER OPTIONAL, + * rsaPublicExponent [200] EXPLICIT INTEGER OPTIONAL, + * mgfDigest [203] EXPLICIT SET OF INTEGER OPTIONAL, + * rollbackResistance [303] EXPLICIT NULL OPTIONAL, + * earlyBootOnly [305] EXPLICIT NULL OPTIONAL, + * activeDateTime [400] EXPLICIT INTEGER OPTIONAL, + * originationExpireDateTime [401] EXPLICIT INTEGER OPTIONAL, + * usageExpireDateTime [402] EXPLICIT INTEGER OPTIONAL, + * usageCountLimit [405] EXPLICIT INTEGER OPTIONAL, + * userSecureId [502] EXPLICIT INTEGER OPTIONAL, -- Only used on key import + * noAuthRequired [503] EXPLICIT NULL OPTIONAL, + * userAuthType [504] EXPLICIT INTEGER OPTIONAL, + * authTimeout [505] EXPLICIT INTEGER OPTIONAL, + * allowWhileOnBody [506] EXPLICIT NULL OPTIONAL, + * trustedUserPresenceReq [507] EXPLICIT NULL OPTIONAL, + * trustedConfirmationReq [508] EXPLICIT NULL OPTIONAL, + * unlockedDeviceReq [509] EXPLICIT NULL OPTIONAL, + * creationDateTime [701] EXPLICIT INTEGER OPTIONAL, + * origin [702] EXPLICIT INTEGER OPTIONAL, + * rootOfTrust [704] EXPLICIT RootOfTrust OPTIONAL, + * osVersion [705] EXPLICIT INTEGER OPTIONAL, + * osPatchLevel [706] EXPLICIT INTEGER OPTIONAL, + * attestationApplicationId [709] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdBrand [710] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdDevice [711] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdProduct [712] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdSerial [713] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdImei [714] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdMeid [715] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdManufacturer [716] EXPLICIT OCTET_STRING OPTIONAL, + * attestationIdModel [717] EXPLICIT OCTET_STRING OPTIONAL, + * vendorPatchLevel [718] EXPLICIT INTEGER OPTIONAL, + * bootPatchLevel [719] EXPLICIT INTEGER OPTIONAL, + * deviceUniqueAttestation [720] EXPLICIT NULL OPTIONAL, + * attestationIdSecondImei [723] EXPLICIT OCTET_STRING OPTIONAL, + * -- moduleHash contains a SHA-256 hash of DER-encoded `Modules` + * moduleHash [724] EXPLICIT OCTET_STRING OPTIONAL, + * } + */ + Certificate[] certificateChain; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyFormat.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyFormat.aidl new file mode 100644 index 0000000..3faef38 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyFormat.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Formats for key import and export. + * @hide + */ +@VintfStability +@Backing(type="int") +enum KeyFormat { + /** X.509 certificate format, for public key export. */ + X509 = 0, + /** PKCS#8 format, asymmetric key pair import. */ + PKCS8 = 1, + /** + * Raw bytes, for symmetric key import, and for import of raw asymmetric keys for curve 25519. + */ + RAW = 3, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl new file mode 100644 index 0000000..b82dee6 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.SecurityLevel; + +/** + * KeyMintHardwareInfo is the hardware information returned by calling KeyMint getHardwareInfo() + * @hide + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeyMintHardwareInfo { + /** + * Implementation version of the keymint hardware. The version number is implementation + * defined, and not necessarily globally meaningful. The version is used to distinguish + * between different versions of a given implementation. + */ + int versionNumber; + + /* securityLevel is the security level of the IKeyMintDevice implementation accessed + * through this aidl package. */ + SecurityLevel securityLevel = SecurityLevel.SOFTWARE; + + /* keyMintName is the name of the IKeyMintDevice implementation. */ + @utf8InCpp String keyMintName; + + /* keyMintAuthorName is the name of the author of the IKeyMintDevice implementation + * (organization name, not individual). This name is implementation defined, + * so it can be used to distinguish between different implementations from the + * same author. + */ + @utf8InCpp String keyMintAuthorName; + + /* The timestampTokenRequired is a boolean flag, which when true reflects that IKeyMintDevice + * instance will expect a valid TimeStampToken with various operations. This will typically + * required by the StrongBox implementations that generally don't have secure clock hardware to + * generate timestamp tokens. + */ + boolean timestampTokenRequired; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyOrigin.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyOrigin.aidl new file mode 100644 index 0000000..5840c6b --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyOrigin.aidl @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * The origin of a key (or pair), i.e. where it was generated. Note that ORIGIN can be found in + * either the hardware-enforced or software-enforced list for a key, indicating whether the key is + * hardware or software-based. Specifically, a key with GENERATED in the hardware-enforced list + * must be guaranteed never to have existed outside the secure hardware. + * @hide + */ +@VintfStability +@Backing(type="int") +enum KeyOrigin { + /** Generated in keyMint. Should not exist outside the TEE. */ + GENERATED = 0, + + /** Derived inside keyMint. Likely exists off-device. */ + DERIVED = 1, + + /** Imported into keyMint. Existed as cleartext in Android. */ + IMPORTED = 2, + + /** Previously used for another purpose that is now obsolete. */ + RESERVED = 3, + + /** + * Securely imported into KeyMint. Was created elsewhere, and passed securely through Android + * to secure hardware. + */ + SECURELY_IMPORTED = 4, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyParameter.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyParameter.aidl new file mode 100644 index 0000000..b69e678 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyParameter.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.KeyParameterValue; +import android.hardware.security.keymint.Tag; + +/** + * Identifies the key authorization parameters to be used with keyMint. This is usually + * provided as an array of KeyParameters to IKeyMintDevice or Operation. + * @hide + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeyParameter { + Tag tag = Tag.INVALID; + KeyParameterValue value; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyParameterValue.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyParameterValue.aidl new file mode 100644 index 0000000..924f402 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyParameterValue.aidl @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.Algorithm; +import android.hardware.security.keymint.BlockMode; +import android.hardware.security.keymint.Digest; +import android.hardware.security.keymint.EcCurve; +import android.hardware.security.keymint.HardwareAuthenticatorType; +import android.hardware.security.keymint.KeyOrigin; +import android.hardware.security.keymint.KeyPurpose; +import android.hardware.security.keymint.PaddingMode; +import android.hardware.security.keymint.SecurityLevel; + +/** @hide */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +union KeyParameterValue { + /* Represents an invalid value type. */ + int invalid; + + /* Enum types */ + Algorithm algorithm; + BlockMode blockMode; + PaddingMode paddingMode; + Digest digest; + EcCurve ecCurve; + KeyOrigin origin; + KeyPurpose keyPurpose; + HardwareAuthenticatorType hardwareAuthenticatorType; + SecurityLevel securityLevel; + + /* Other types */ + boolean boolValue; // Always true, if present. + int integer; + long longInteger; + long dateTime; // In milliseconds from epoch + + byte[] blob; +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyPurpose.aidl b/libs/rust/aidl/android/hardware/security/keymint/KeyPurpose.aidl new file mode 100644 index 0000000..32e71a7 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/KeyPurpose.aidl @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Possible purposes of a key (or pair). + * @hide + */ +@VintfStability +@Backing(type="int") +enum KeyPurpose { + /* Usable with 3DES and AES keys. */ + ENCRYPT = 0, + + /* Usable with RSA, 3DES and AES keys. */ + DECRYPT = 1, + + /* Usable with RSA, EC and HMAC keys. */ + SIGN = 2, + + /* Usable with HMAC keys. */ + VERIFY = 3, + + /* 4 is reserved */ + + /* Usable with wrapping keys. */ + WRAP_KEY = 5, + + /* Key Agreement, usable with EC keys. */ + AGREE_KEY = 6, + + /* Usable as an attestation signing key. Keys with this purpose must not have any other + * purpose; if they do, key generation/import must be rejected with + * ErrorCode::INCOMPATIBLE_PURPOSE. (Rationale: If key also included KeyPurpose::SIGN, then + * it could be used to sign arbitrary data, including any tbsCertificate, and so an + * attestation produced by the key would have no security properties.) + */ + ATTEST_KEY = 7, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/PaddingMode.aidl b/libs/rust/aidl/android/hardware/security/keymint/PaddingMode.aidl new file mode 100644 index 0000000..6ff4b29 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/PaddingMode.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Padding modes that may be applied to plaintext for encryption operations. This list includes + * padding modes for both symmetric and asymmetric algorithms. Note that implementations should not + * provide all possible combinations of algorithm and padding, only the + * cryptographically-appropriate pairs. + * @hide + */ +@VintfStability +@Backing(type="int") +enum PaddingMode { + NONE = 1, + RSA_OAEP = 2, + RSA_PSS = 3, + RSA_PKCS1_1_5_ENCRYPT = 4, + RSA_PKCS1_1_5_SIGN = 5, + PKCS7 = 64, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/SecurityLevel.aidl b/libs/rust/aidl/android/hardware/security/keymint/SecurityLevel.aidl new file mode 100644 index 0000000..80c63b2 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/SecurityLevel.aidl @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * Device security levels. These enum values are used in two ways: + * + * 1. Returned from IKeyMintDevice::getHardwareInfo to identify the security level of the + * IKeyMintDevice. This characterizes the sort of environment in which the KeyMint + * implementation runs, and therefore the security of its operations. + * + * 2. Associated with individual KeyMint authorization Tags in KeyCharacteristics or in attestation + * certificates. This specifies the security level of the weakest environment involved in + * enforcing that particular tag, i.e. the sort of security environment an attacker would have + * to subvert in order to break the enforcement of that tag. + * @hide + */ +@VintfStability +@Backing(type="int") +enum SecurityLevel { + /** + * The SOFTWARE security level represents a KeyMint implementation that runs in an Android + * process, or a tag enforced by such an implementation. An attacker who can compromise that + * process, or obtain root, or subvert the kernel on the device can defeat it. + * + * Note that the distinction between SOFTWARE and KEYSTORE is only relevant on-device. For + * attestation purposes, these categories are combined into the software-enforced authorization + * list. + */ + SOFTWARE = 0, + + /** + * The TRUSTED_ENVIRONMENT security level represents a KeyMint implementation that runs in an + * isolated execution environment that is securely isolated from the code running on the kernel + * and above, and which satisfies the requirements specified in CDD 9.11.1 [C-1-2]. An attacker + * who completely compromises Android, including the Linux kernel, does not have the ability to + * subvert it. An attacker who can find an exploit that gains them control of the trusted + * environment, or who has access to the physical device and can mount a sophisticated hardware + * attack, may be able to defeat it. + */ + TRUSTED_ENVIRONMENT = 1, + + /** + * The STRONGBOX security level represents a KeyMint implementation that runs in security + * hardware that satisfies the requirements specified in CDD 9.11.2. Roughly speaking, these + * are discrete, security-focus computing environments that are hardened against physical and + * side channel attack, and have had their security formally validated by a competent + * penetration testing lab. + */ + STRONGBOX = 2, + + /** + * KeyMint implementations must never return the KEYSTORE security level from getHardwareInfo. + * It is used to specify tags that are not enforced by the IKeyMintDevice, but are instead + * to be enforced by Keystore. An attacker who can subvert the keystore process or gain root or + * subvert the kernel can prevent proper enforcement of these tags. + * + * + * Note that the distinction between SOFTWARE and KEYSTORE is only relevant on-device. When + * KeyMint generates an attestation certificate, these categories are combined into the + * software-enforced authorization list. + */ + KEYSTORE = 100 +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/Tag.aidl b/libs/rust/aidl/android/hardware/security/keymint/Tag.aidl new file mode 100644 index 0000000..7ea5f5d --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/Tag.aidl @@ -0,0 +1,1017 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.TagType; + +/** + * Tag specifies various kinds of tags that can be set in KeyParameter to identify what kind of + * data are stored in KeyParameter. + * @hide + */ +@VintfStability +@Backing(type="int") +enum Tag { + /** + * Tag::INVALID should never be set. It means you hit an error. + */ + INVALID = 0, + + /** + * Tag::PURPOSE specifies the set of purposes for which the key may be used. Possible values + * are defined in the KeyPurpose enumeration. + * + * This tag is repeatable; keys may be generated with multiple values, although an operation has + * a single purpose. When begin() is called to start an operation, the purpose of the operation + * is specified. If the purpose specified for the operation is not authorized by the key (the + * key didn't have a corresponding Tag::PURPOSE provided during generation/import), the + * operation must fail with ErrorCode::INCOMPATIBLE_PURPOSE. + * + * Must be hardware-enforced. + */ + PURPOSE = TagType.ENUM_REP | 1, + + /** + * Tag::ALGORITHM specifies the cryptographic algorithm with which the key is used. This tag + * must be provided to generateKey and importKey, and must be specified in the wrapped key + * provided to importWrappedKey. + * + * Must be hardware-enforced. + */ + ALGORITHM = TagType.ENUM | 2, + + /** + * Tag::KEY_SIZE specifies the size, in bits, of the key, measuring in the normal way for the + * key's algorithm. For example, for RSA keys, Tag::KEY_SIZE specifies the size of the public + * modulus. For AES keys it specifies the length of the secret key material. For 3DES keys it + * specifies the length of the key material, not counting parity bits (though parity bits must + * be provided for import, etc.). Since only three-key 3DES keys are supported, 3DES + * Tag::KEY_SIZE must be 168. + * + * Must be hardware-enforced. + */ + KEY_SIZE = TagType.UINT | 3, + + /** + * Tag::BLOCK_MODE specifies the block cipher mode(s) with which the key may be used. This tag + * is only relevant to AES and 3DES keys. Possible values are defined by the BlockMode enum. + * + * This tag is repeatable for key generation/import. For AES and 3DES operations the caller + * must specify a Tag::BLOCK_MODE in the params argument of begin(). If the mode is missing or + * the specified mode is not in the modes specified for the key during generation/import, the + * operation must fail with ErrorCode::INCOMPATIBLE_BLOCK_MODE. + * + * Must be hardware-enforced. + */ + BLOCK_MODE = TagType.ENUM_REP | 4, + + /** + * Tag::DIGEST specifies the digest algorithms that may be used with the key to perform signing + * and verification operations. This tag is relevant to RSA, ECDSA and HMAC keys. Possible + * values are defined by the Digest enum. + * + * This tag is repeatable for key generation/import. For signing and verification operations, + * the caller must specify a digest in the params argument of begin(). If the digest is missing + * or the specified digest is not in the digests associated with the key, the operation must + * fail with ErrorCode::INCOMPATIBLE_DIGEST. + * + * Must be hardware-enforced. + */ + DIGEST = TagType.ENUM_REP | 5, + + /** + * Tag::PADDING specifies the padding modes that may be used with the key. This tag is relevant + * to RSA, AES and 3DES keys. Possible values are defined by the PaddingMode enum. + * + * PaddingMode::RSA_OAEP and PaddingMode::RSA_PKCS1_1_5_ENCRYPT are used only for RSA + * encryption/decryption keys and specify RSA OAEP padding and RSA PKCS#1 v1.5 randomized + * padding, respectively. PaddingMode::RSA_PSS and PaddingMode::RSA_PKCS1_1_5_SIGN are used + * only for RSA signing/verification keys and specify RSA PSS padding and RSA PKCS#1 v1.5 + * deterministic padding, respectively. + * + * PaddingMode::NONE may be used with either RSA, AES or 3DES keys. For AES or 3DES keys, if + * PaddingMode::NONE is used with block mode ECB or CBC and the data to be encrypted or + * decrypted is not a multiple of the AES block size in length, the call to finish() must fail + * with ErrorCode::INVALID_INPUT_LENGTH. + * + * PaddingMode::PKCS7 may only be used with AES and 3DES keys, and only with ECB and CBC modes. + * + * In any case, if the caller specifies a padding mode that is not usable with the key's + * algorithm, the generation or import method must return ErrorCode::INCOMPATIBLE_PADDING_MODE. + * + * This tag is repeatable. A padding mode must be specified in the call to begin(). If the + * specified mode is not authorized for the key, the operation must fail with + * ErrorCode::INCOMPATIBLE_BLOCK_MODE. + * + * Must be hardware-enforced. + */ + PADDING = TagType.ENUM_REP | 6, + + /** + * Tag::CALLER_NONCE specifies that the caller can provide a nonce for nonce-requiring + * operations. This tag is boolean, so the possible values are true (if the tag is present) and + * false (if the tag is not present). + * + * This tag is used only for AES and 3DES keys, and is only relevant for CBC, CTR and GCM block + * modes. If the tag is not present in a key's authorization list, implementations must reject + * any operation that provides Tag::NONCE to begin() with ErrorCode::CALLER_NONCE_PROHIBITED. + * + * Must be hardware-enforced. + */ + CALLER_NONCE = TagType.BOOL | 7, + + /** + * Tag::MIN_MAC_LENGTH specifies the minimum length of MAC that can be requested or verified + * with this key for HMAC keys and AES keys that support GCM mode. + * + * This value is the minimum MAC length, in bits. It must be a multiple of 8 bits. For HMAC + * keys, the value must be least 64 and no more than 512. For GCM keys, the value must be at + * least 96 and no more than 128. If the provided value violates these requirements, + * generateKey() or importKey() must return ErrorCode::UNSUPPORTED_MIN_MAC_LENGTH. + * + * Must be hardware-enforced. + */ + MIN_MAC_LENGTH = TagType.UINT | 8, + + // Tag 9 reserved + + /** + * Tag::EC_CURVE specifies the elliptic curve. Possible values are defined in the EcCurve + * enumeration. + * + * Must be hardware-enforced. + */ + EC_CURVE = TagType.ENUM | 10, + + /** + * Tag::RSA_PUBLIC_EXPONENT specifies the value of the public exponent for an RSA key pair. + * This tag is relevant only to RSA keys, and is required for all RSA keys. + * + * The value is a 64-bit unsigned integer that satisfies the requirements of an RSA public + * exponent. This value must be a prime number. IKeyMintDevice implementations must support + * the value 2^16+1 and may support other reasonable values. If no exponent is specified or if + * the specified exponent is not supported, key generation must fail with + * ErrorCode::INVALID_ARGUMENT. + * + * Must be hardware-enforced. + */ + RSA_PUBLIC_EXPONENT = TagType.ULONG | 200, + + // Tag 201 reserved + + /** + * Tag::INCLUDE_UNIQUE_ID is specified during key generation to indicate that an attestation + * certificate for the generated key should contain an application-scoped and time-bounded + * device-unique ID. See Tag::UNIQUE_ID. + * + * Must be hardware-enforced. + */ + INCLUDE_UNIQUE_ID = TagType.BOOL | 202, + + /** + * Tag::RSA_OAEP_MGF_DIGEST specifies the MGF1 digest algorithms that may be used with RSA + * encryption/decryption with OAEP padding. Possible values are defined by the Digest enum. + * + * This tag is repeatable for key generation/import. + * + * If the caller specifies an MGF1 digest in the params argument of begin(), that digest must be + * present as an RSA_OAEP_MGF_DIGEST value in the key characteristics (or the begin() operation + * must fail with ErrorCode::INCOMPATIBLE_MGF_DIGEST). + * + * If the caller does not specify an MGF1 digest in the params argument of begin(), a default + * MGF1 digest of SHA1 is used. If the key characteristics have any explicitly specified values + * for RSA_OAEP_MGF_DIGEST, then SHA1 must be included (or the begin() operation must fail with + * ErrorCode::INCOMPATIBLE_MGF_DIGEST). + * + * Must be hardware-enforced. + */ + RSA_OAEP_MGF_DIGEST = TagType.ENUM_REP | 203, + + // Tag 301 reserved + + /** + * Tag::BOOTLOADER_ONLY specifies only the bootloader can use the key. + * + * Any attempt to use a key with Tag::BOOTLOADER_ONLY from the Android system must fail with + * ErrorCode::INVALID_KEY_BLOB. + * + * Must be hardware-enforced. + */ + BOOTLOADER_ONLY = TagType.BOOL | 302, + + /** + * Tag::ROLLBACK_RESISTANCE specifies that the key has rollback resistance, meaning that when + * deleted with deleteKey() or deleteAllKeys(), the key is guaranteed to be permanently deleted + * and unusable. It's possible that keys without this tag could be deleted and then restored + * from backup. + * + * This tag is specified by the caller during key generation or import to require. If the + * IKeyMintDevice cannot guarantee rollback resistance for the specified key, it must return + * ErrorCode::ROLLBACK_RESISTANCE_UNAVAILABLE. IKeyMintDevice implementations are not + * required to support rollback resistance. + * + * Must be hardware-enforced. + */ + ROLLBACK_RESISTANCE = TagType.BOOL | 303, + + // Reserved for future use. + HARDWARE_TYPE = TagType.ENUM | 304, + + /** + * Keys tagged with EARLY_BOOT_ONLY may only be used during early boot, until + * IKeyMintDevice::earlyBootEnded() is called. Early boot keys may be created after + * early boot. Early boot keys may not be imported at all, if Tag::EARLY_BOOT_ONLY is + * provided to IKeyMintDevice::importKey, the import must fail with + * ErrorCode::EARLY_BOOT_ENDED. + */ + EARLY_BOOT_ONLY = TagType.BOOL | 305, + + /** + * Tag::ACTIVE_DATETIME specifies the date and time at which the key becomes active, in + * milliseconds since Jan 1, 1970. If a key with this tag is used prior to the specified date + * and time, IKeyMintDevice::begin() must return ErrorCode::KEY_NOT_YET_VALID; + * + * Need not be hardware-enforced. + */ + ACTIVE_DATETIME = TagType.DATE | 400, + + /** + * Tag::ORIGINATION_EXPIRE_DATETIME specifies the date and time at which the key expires for + * signing and encryption purposes. After this time, any attempt to use a key with + * KeyPurpose::SIGN or KeyPurpose::ENCRYPT provided to begin() must fail with + * ErrorCode::KEY_EXPIRED. + * + * The value is a 64-bit integer representing milliseconds since January 1, 1970. + * + * Need not be hardware-enforced. + */ + ORIGINATION_EXPIRE_DATETIME = TagType.DATE | 401, + + /** + * Tag::USAGE_EXPIRE_DATETIME specifies the date and time at which the key expires for + * verification and decryption purposes. After this time, any attempt to use a key with + * KeyPurpose::VERIFY or KeyPurpose::DECRYPT provided to begin() must fail with + * ErrorCode::KEY_EXPIRED. + * + * The value is a 64-bit integer representing milliseconds since January 1, 1970. + * + * Need not be hardware-enforced. + */ + USAGE_EXPIRE_DATETIME = TagType.DATE | 402, + + /** + * OBSOLETE: Do not use. + * + * This tag value is included for historical reason, as it was present in Keymaster. + * KeyMint implementations do not need to support this tag. + */ + MIN_SECONDS_BETWEEN_OPS = TagType.UINT | 403, + + /** + * Tag::MAX_USES_PER_BOOT specifies the maximum number of times that a key may be used between + * system reboots. This is another mechanism to rate-limit key use. + * + * The value is a 32-bit integer representing uses per boot. + * + * When a key with this tag is used in an operation, a key-associated counter must be + * incremented during the begin() call. After the key counter has exceeded this value, all + * subsequent attempts to use the key must fail with ErrorCode::MAX_OPS_EXCEEDED, until the + * device is restarted. This implies that the IKeyMintDevice must keep a table of use + * counters for keys with this tag. Because KeyMint memory is often limited, this table can + * have a fixed maximum size and KeyMint can fail operations that attempt to use keys with + * this tag when the table is full. The table needs to accommodate at least 8 keys. If an + * operation fails because the table is full, IKeyMintDevice must + * ErrorCode::TOO_MANY_OPERATIONS. + * + * Must be hardware-enforced. + */ + MAX_USES_PER_BOOT = TagType.UINT | 404, + + /** + * Tag::USAGE_COUNT_LIMIT specifies the number of times that a key may be used. This can be + * used to limit the use of a key. + * + * The value is a 32-bit integer representing the current number of attempts left. + * + * When initializing a limited use key, the value of this tag represents the maximum usage + * limit for that key. After the key usage is exhausted, the key blob should be invalidated by + * finish() call. Any subsequent attempts to use the key must result in a failure with + * ErrorCode::INVALID_KEY_BLOB returned by IKeyMintDevice. + * + * At this point, if the caller specifies count > 1, it is not expected that any TEE will be + * able to enforce this feature in the hardware due to limited resources of secure + * storage. In this case, the tag with the value of maximum usage must be added to the key + * characteristics with SecurityLevel::KEYSTORE by the IKeyMintDevice. + * + * On the other hand, if the caller specifies count = 1, some TEEs may have the ability + * to enforce this feature in the hardware with its secure storage. If the IKeyMintDevice + * implementation can enforce this feature, the tag with value = 1 must be added to the key + * characteristics with the SecurityLevel of the IKeyMintDevice. If the IKeyMintDevice can't + * enforce this feature even when the count = 1, the tag must be added to the key + * characteristics with the SecurityLevel::KEYSTORE. + * + * When the key is attested, this tag with the same value must also be added to the attestation + * record. This tag must have the same SecurityLevel as the tag that is added to the key + * characteristics. + */ + USAGE_COUNT_LIMIT = TagType.UINT | 405, + + /** + * Tag::USER_ID specifies the ID of the Android user that is permitted to use the key. + * + * Must not be hardware-enforced. + */ + USER_ID = TagType.UINT | 501, + + /** + * Tag::USER_SECURE_ID specifies that a key may only be used under a particular secure user + * authentication state. This tag is mutually exclusive with Tag::NO_AUTH_REQUIRED. + * + * The value is a 64-bit integer specifying the authentication policy state value which must be + * present in the userId or authenticatorId field of a HardwareAuthToken provided to begin(), + * update(), or finish(). If a key with Tag::USER_SECURE_ID is used without a HardwareAuthToken + * with the matching userId or authenticatorId, the IKeyMintDevice must return + * ErrorCode::KEY_USER_NOT_AUTHENTICATED. + * + * Tag::USER_SECURE_ID interacts with Tag::AUTH_TIMEOUT in a very important way. If + * Tag::AUTH_TIMEOUT is present in the key's characteristics then the key is a "timeout-based" + * key, and may only be used if the difference between the current time when begin() is called + * and the timestamp in the HardwareAuthToken is less than the value in Tag::AUTH_TIMEOUT * 1000 + * (the multiplier is because Tag::AUTH_TIMEOUT is in seconds, but the HardwareAuthToken + * timestamp is in milliseconds). Otherwise the IKeyMintDevice must return + * ErrorCode::KEY_USER_NOT_AUTHENTICATED. + * + * If Tag::AUTH_TIMEOUT is not present, then the key is an "auth-per-operation" key. In this + * case, begin() must not require a HardwareAuthToken with appropriate contents. Instead, + * update() and finish() must receive a HardwareAuthToken with Tag::USER_SECURE_ID value in + * userId or authenticatorId fields, and the current operation's operation handle in the + * challenge field. Otherwise the IKeyMintDevice must return + * ErrorCode::KEY_USER_NOT_AUTHENTICATED. + * + * This tag is repeatable. If repeated, and any one of the values matches the HardwareAuthToken + * as described above, the key is authorized for use. Otherwise the operation must fail with + * ErrorCode::KEY_USER_NOT_AUTHENTICATED. + * + * Must be hardware-enforced. + */ + USER_SECURE_ID = TagType.ULONG_REP | 502, + + /** + * Tag::NO_AUTH_REQUIRED specifies that no authentication is required to use this key. This tag + * is mutually exclusive with Tag::USER_SECURE_ID. + * + * Must be hardware-enforced. + */ + NO_AUTH_REQUIRED = TagType.BOOL | 503, + + /** + * Tag::USER_AUTH_TYPE specifies the types of user authenticators that may be used to authorize + * this key. + * + * The value is one or more values from HardwareAuthenticatorType, ORed together. + * + * When IKeyMintDevice is requested to perform an operation with a key with this tag, it must + * receive a HardwareAuthToken and one or more bits must be set in both the HardwareAuthToken's + * authenticatorType field and the Tag::USER_AUTH_TYPE value. That is, it must be true that + * + * (token.authenticatorType & tag_user_auth_type) != 0 + * + * where token.authenticatorType is the authenticatorType field of the HardwareAuthToken and + * tag_user_auth_type is the value of Tag:USER_AUTH_TYPE. + * + * Must be hardware-enforced. + */ + USER_AUTH_TYPE = TagType.ENUM | 504, + + /** + * Tag::AUTH_TIMEOUT specifies the time in seconds for which the key is authorized for use, + * after user authentication. If + * Tag::USER_SECURE_ID is present and this tag is not, then the key requires authentication for + * every usage (see begin() for the details of the authentication-per-operation flow). + * + * The value is a 32-bit integer specifying the time in seconds after a successful + * authentication of the user specified by Tag::USER_SECURE_ID with the authentication method + * specified by Tag::USER_AUTH_TYPE that the key can be used. + * + * Must be hardware-enforced. + */ + AUTH_TIMEOUT = TagType.UINT | 505, + + /** + * Tag::ALLOW_WHILE_ON_BODY specifies that the key may be used after authentication timeout if + * device is still on-body (requires on-body sensor). + * + * Cannot be hardware-enforced. + */ + ALLOW_WHILE_ON_BODY = TagType.BOOL | 506, + + /** + * TRUSTED_USER_PRESENCE_REQUIRED is an optional feature that specifies that this key must be + * unusable except when the user has provided proof of physical presence. Proof of physical + * presence must be a signal that cannot be triggered by an attacker who doesn't have one of: + * + * a) Physical control of the device or + * + * b) Control of the secure environment that holds the key. + * + * For instance, proof of user identity may be considered proof of presence if it meets the + * requirements. However, proof of identity established in one security domain (e.g. TEE) does + * not constitute proof of presence in another security domain (e.g. StrongBox), and no + * mechanism analogous to the authentication token is defined for communicating proof of + * presence across security domains. + * + * Some examples: + * + * A hardware button hardwired to a pin on a StrongBox device in such a way that nothing + * other than a button press can trigger the signal constitutes proof of physical presence + * for StrongBox keys. + * + * Fingerprint authentication provides proof of presence (and identity) for TEE keys if the + * TEE has exclusive control of the fingerprint scanner and performs fingerprint matching. + * + * Password authentication does not provide proof of presence to either TEE or StrongBox, + * even if TEE or StrongBox does the password matching, because password input is handled by + * the non-secure world, which means an attacker who has compromised Android can spoof + * password authentication. + * + * Note that no mechanism is defined for delivering proof of presence to an IKeyMintDevice, + * except perhaps as implied by an auth token. This means that KeyMint must be able to check + * proof of presence some other way. Further, the proof of presence must be performed between + * begin() and the first call to update() or finish(). If the first update() or the finish() + * call is made without proof of presence, the keyMint method must return + * ErrorCode::PROOF_OF_PRESENCE_REQUIRED and abort the operation. The caller must delay the + * update() or finish() call until proof of presence has been provided, which means the caller + * must also have some mechanism for verifying that the proof has been provided. + * + * Only one operation requiring TUP may be in flight at a time. If begin() has already been + * called on one key with TRUSTED_USER_PRESENCE_REQUIRED, and another begin() comes in for that + * key or another with TRUSTED_USER_PRESENCE_REQUIRED, KeyMint must return + * ErrorCode::CONCURRENT_PROOF_OF_PRESENCE_REQUESTED. + * + * Must be hardware-enforced. + */ + TRUSTED_USER_PRESENCE_REQUIRED = TagType.BOOL | 507, + + /** + * Tag::TRUSTED_CONFIRMATION_REQUIRED is only applicable to keys with KeyPurpose SIGN, and + * specifies that this key must not be usable unless the user provides confirmation of the data + * to be signed. Confirmation is proven to keyMint via an approval token. See the authToken + * parameter of begin(), as well as the ConfirmationUI HAL. + * + * If an attempt to use a key with this tag does not have a cryptographically valid + * token provided to finish() or if the data provided to update()/finish() does not + * match the data described in the token, keyMint must return NO_USER_CONFIRMATION. + * + * Must be hardware-enforced. + */ + TRUSTED_CONFIRMATION_REQUIRED = TagType.BOOL | 508, + + /** + * Tag::UNLOCKED_DEVICE_REQUIRED specifies that the key may only be used when the device is + * unlocked. + * + * This tag was originally intended to be hardware-enforced. However, the support for hardware + * enforcement of this tag is now considered deprecated because it cannot work correctly, and + * even if implemented it does nothing because it was never enabled by Keystore. Refer to the + * documentation for the deprecated method IKeyMintDevice::deviceLocked(). + */ + UNLOCKED_DEVICE_REQUIRED = TagType.BOOL | 509, + + /** + * Tag::APPLICATION_ID. When provided to generateKey or importKey, this tag specifies data + * that is necessary during all uses of the key. In particular, calls to exportKey() and + * getKeyCharacteristics() must provide the same value to the clientId parameter, and calls to + * begin() must provide this tag and the same associated data as part of the inParams set. If + * the correct data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB. Note + * that a key with a zero-length APPLICATION_ID cannot have its key characteristics retrieved + * using getKeyCharacteristics() due to a historical limitation of the API. + * + * The content of this tag must be bound to the key cryptographically, meaning it must not be + * possible for an adversary who has access to all of the secure world secrets but does not have + * access to the tag content to decrypt the key without brute-forcing the tag content, which + * applications can prevent by specifying sufficiently high-entropy content. + * + * Must never appear in KeyCharacteristics. + */ + APPLICATION_ID = TagType.BYTES | 601, + + /* + * Semantically unenforceable tags, either because they have no specific meaning or because + * they're informational only. + */ + + /** + * Tag::APPLICATION_DATA. When provided to generateKey or importKey, this tag specifies data + * that is necessary during all uses of the key. In particular, calls to begin() and + * exportKey() must provide the same value to the appData parameter, and calls to begin must + * provide this tag and the same associated data as part of the inParams set. If the correct + * data is not provided, the method must return ErrorCode::INVALID_KEY_BLOB. Note that a key + * with a zero-length APPLICATION_DATA cannot have its key characteristics retrieved using + * getKeyCharacteristics() due to a historical limitation of the API. + * + * The content of this tag must be bound to the key cryptographically, meaning it must not be + * possible for an adversary who has access to all of the secure world secrets but does not have + * access to the tag content to decrypt the key without brute-forcing the tag content, which + * applications can prevent by specifying sufficiently high-entropy content. + * + * Must never appear in KeyCharacteristics. + */ + APPLICATION_DATA = TagType.BYTES | 700, + + /** + * Tag::CREATION_DATETIME specifies the date and time the key was created, in milliseconds since + * January 1, 1970. This tag is optional and informational only, and not enforced by anything. + * + * Must be in the software-enforced list, if provided. + */ + CREATION_DATETIME = TagType.DATE | 701, + + /** + * Tag::ORIGIN specifies where the key was created, if known. This tag must not be specified + * during key generation or import, and must be added to the key characteristics by the + * IKeyMintDevice. The possible values are defined in the KeyOrigin enum. + * + * Must be hardware-enforced. + */ + ORIGIN = TagType.ENUM | 702, + + // 703 is unused. + + /** + * Tag::ROOT_OF_TRUST specifies the root of trust associated with the key used by verified boot + * to validate the system. It describes the boot key, verified boot state, boot hash, and + * whether device is locked. This tag is never provided to or returned from KeyMint in the + * key characteristics. It exists only to define the tag for use in the attestation record. + * + * Must never appear in KeyCharacteristics. + */ + ROOT_OF_TRUST = TagType.BYTES | 704, + + /** + * Tag::OS_VERSION specifies the system OS version with which the key may be used. This tag is + * never sent to the IKeyMintDevice, but is added to the hardware-enforced authorization list + * by the TA. Any attempt to use a key with a Tag::OS_VERSION value different from the + * currently-running OS version must cause begin(), getKeyCharacteristics() or exportKey() to + * return ErrorCode::KEY_REQUIRES_UPGRADE. See upgradeKey() for details. + * + * The value of the tag is an integer of the form MMmmss, where MM is the major version number, + * mm is the minor version number, and ss is the sub-minor version number. For example, for a + * key generated on Android version 4.0.3, the value would be 040003. + * + * The IKeyMintDevice HAL must read the current OS version from the system property + * ro.build.version.release and deliver it to the secure environment when the HAL is first + * loaded (mechanism is implementation-defined). The secure environment must not accept another + * version until after the next boot. If the content of ro.build.version.release has additional + * version information after the sub-minor version number, it must not be included in + * Tag::OS_VERSION. If the content is non-numeric, the secure environment must use 0 as the + * system version. + * + * Must be hardware-enforced. + */ + OS_VERSION = TagType.UINT | 705, + + /** + * Tag::OS_PATCHLEVEL specifies the system security patch level with which the key may be used. + * This tag is never sent to the keyMint TA, but is added to the hardware-enforced + * authorization list by the TA. Any attempt to use a key with a Tag::OS_PATCHLEVEL value + * different from the currently-running system patchlevel must cause begin(), + * getKeyCharacteristics() or exportKey() to return ErrorCode::KEY_REQUIRES_UPGRADE. See + * upgradeKey() for details. + * + * The value of the tag is an integer of the form YYYYMM, where YYYY is the four-digit year of + * the last update and MM is the two-digit month of the last update. For example, for a key + * generated on an Android device last updated in December 2015, the value would be 201512. + * + * The IKeyMintDevice HAL must read the current system patchlevel from the system property + * ro.build.version.security_patch and deliver it to the secure environment when the HAL is + * first loaded (mechanism is implementation-defined). The secure environment must not accept + * another patchlevel until after the next boot. + * + * Must be hardware-enforced. + */ + OS_PATCHLEVEL = TagType.UINT | 706, + + /** + * Tag::UNIQUE_ID specifies a unique, time-based identifier. This tag is never provided to or + * returned from KeyMint in the key characteristics. It exists only to define the tag for use + * in the attestation record. + * + * When a key with Tag::INCLUDE_UNIQUE_ID is attested, the unique ID is added to the attestation + * record. The value is a 128-bit hash that is unique per device and per calling application, + * and changes monthly and on most password resets. It is computed with: + * + * HMAC_SHA256(T || C || R, HBK) + * + * Where: + * + * T is the "temporal counter value", computed by dividing the value of + * Tag::CREATION_DATETIME by 2592000000, dropping any remainder. T changes every 30 days + * (2592000000 = 30 * 24 * 60 * 60 * 1000). + * + * C is the value of Tag::ATTESTATION_APPLICATION_ID that is provided to attested key + * generation/import operations. + * + * R is 1 if Tag::RESET_SINCE_ID_ROTATION was provided to attested key generation/import or 0 + * if the tag was not provided. + * + * HBK is a unique hardware-bound secret known to the secure environment and never revealed + * by it. The secret must contain at least 128 bits of entropy and be unique to the + * individual device (probabilistic uniqueness is acceptable). + * + * HMAC_SHA256 is the HMAC function, with SHA-2-256 as the hash. + * + * The output of the HMAC function must be truncated to 128 bits. + * + * Must be hardware-enforced. + */ + UNIQUE_ID = TagType.BYTES | 707, + + /** + * Tag::ATTESTATION_CHALLENGE is used to deliver a "challenge" value to the attested key + * generation/import methods, which must place the value in the KeyDescription SEQUENCE of the + * attestation extension. + * The challenge value may be up to 128 bytes. If the caller provides a bigger challenge, + * INVALID_INPUT_LENGTH error should be returned. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_CHALLENGE = TagType.BYTES | 708, + + /** + * Tag::ATTESTATION_APPLICATION_ID identifies the set of applications which may use a key, used + * only with attested key generation/import operations. + * + * The content of Tag::ATTESTATION_APPLICATION_ID is a DER-encoded ASN.1 structure, with the + * following schema: + * + * AttestationApplicationId ::= SEQUENCE { + * packageInfoRecords SET OF PackageInfoRecord, + * signatureDigests SET OF OCTET_STRING, + * } + * + * PackageInfoRecord ::= SEQUENCE { + * packageName OCTET_STRING, + * version INTEGER, + * } + * + * See system/security/keystore/keystore_attestation_id.cpp for details of construction. + * IKeyMintDevice implementers do not need to create or parse the ASN.1 structure, but only + * copy the tag value into the attestation record. The DER-encoded string must not exceed 1 KiB + * in length. + * + * Cannot be hardware-enforced. + */ + ATTESTATION_APPLICATION_ID = TagType.BYTES | 709, + + /** + * Tag::ATTESTATION_ID_BRAND provides the device's brand name, as returned by Build.BRAND in + * Android, to attested key generation/import operations. This field must be set only when + * requesting attestation of the device's identifiers. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_BRAND = TagType.BYTES | 710, + + /** + * Tag::ATTESTATION_ID_DEVICE provides the device's device name, as returned by Build.DEVICE in + * Android, to attested key generation/import operations. This field must be set only when + * requesting attestation of the device's identifiers. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_DEVICE = TagType.BYTES | 711, + + /** + * Tag::ATTESTATION_ID_PRODUCT provides the device's product name, as returned by Build.PRODUCT + * in Android, to attested key generation/import operations. This field must be set only when + * requesting attestation of the device's identifiers. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_PRODUCT = TagType.BYTES | 712, + + /** + * Tag::ATTESTATION_ID_SERIAL the device's serial number. This field must be set only when + * requesting attestation of the device's identifiers. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_SERIAL = TagType.BYTES | 713, + + /** + * Tag::ATTESTATION_ID_IMEI provides the IMEI one of the radios on the device to attested key + * generation/import operations. This field must be set only when requesting attestation of the + * device's identifiers. If the device has more than one IMEI, a second IMEI may be included + * by using the Tag::ATTESTATION_ID_SECOND_IMEI tag. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_IMEI = TagType.BYTES | 714, + + /** + * Tag::ATTESTATION_ID_MEID provides the MEIDs for all radios on the device to attested key + * generation/import operations. This field must be set only when requesting attestation of the + * device's identifiers. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_MEID = TagType.BYTES | 715, + + /** + * Tag::ATTESTATION_ID_MANUFACTURER provides the device's manufacturer name, as returned by + * Build.MANUFACTURER in Android, to attested key generation/import operations. This field must + * be set only when requesting attestation of the device's identifiers. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_MANUFACTURER = TagType.BYTES | 716, + + /** + * Tag::ATTESTATION_ID_MODEL provides the device's model name, as returned by Build.MODEL in + * Android, to attested key generation/import operations. This field must be set only when + * requesting attestation of the device's identifiers. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_MODEL = TagType.BYTES | 717, + + /** + * Tag::VENDOR_PATCHLEVEL specifies the vendor image security patch level with which the key may + * be used. This tag is never sent to the keyMint TA, but is added to the hardware-enforced + * authorization list by the TA. Any attempt to use a key with a Tag::VENDOR_PATCHLEVEL value + * different from the currently-running system patchlevel must cause begin(), + * getKeyCharacteristics() or exportKey() to return ErrorCode::KEY_REQUIRES_UPGRADE. See + * upgradeKey() for details. + * + * The value of the tag is an integer of the form YYYYMMDD, where YYYY is the four-digit year of + * the last update, MM is the two-digit month and DD is the two-digit day of the last + * update. For example, for a key generated on an Android device last updated on June 5, 2018, + * the value would be 20180605. + * + * The IKeyMintDevice HAL must read the current vendor patchlevel from the system property + * ro.vendor.build.security_patch and deliver it to the secure environment when the HAL is first + * loaded (mechanism is implementation-defined). The secure environment must not accept another + * patchlevel until after the next boot. + * + * Must be hardware-enforced. + */ + VENDOR_PATCHLEVEL = TagType.UINT | 718, + + /** + * Tag::BOOT_PATCHLEVEL specifies the boot image (kernel) security patch level with which the + * key may be used. This tag is never sent to the keyMint TA, but is added to the + * hardware-enforced authorization list by the TA. Any attempt to use a key with a + * Tag::BOOT_PATCHLEVEL value different from the currently-running system patchlevel must + * cause begin(), getKeyCharacteristics() or exportKey() to return + * ErrorCode::KEY_REQUIRES_UPGRADE. See upgradeKey() for details. + * + * The value of the tag is an integer of the form YYYYMMDD, where YYYY is the four-digit year of + * the last update, MM is the two-digit month and DD is the two-digit day of the last + * update. For example, for a key generated on an Android device last updated on June 5, 2018, + * the value would be 20180605. If the day is not known, 00 may be substituted. + * + * During each boot, the bootloader must provide the patch level of the boot image to the secure + * environment (mechanism is implementation-defined). + * + * Must be hardware-enforced. + */ + BOOT_PATCHLEVEL = TagType.UINT | 719, + + /** + * DEVICE_UNIQUE_ATTESTATION is an argument to IKeyMintDevice::attested key generation/import + * operations. It indicates that attestation using a device-unique key is requested, rather + * than a batch key. When a device-unique key is used, the returned chain should contain two or + * three certificates. + * + * In case the chain contains two certificates, they should be: + * * The attestation certificate, containing the attestation extension, as described in + * KeyCreationResult.aidl. + * * A self-signed root certificate, signed by the device-unique key. + * + * In case the chain contains three certificates, they should be: + * * The attestation certificate, containing the attestation extension, as described in + * KeyCreationResult.aidl, signed by the device-unique key. + * * An intermediate certificate, containing the public portion of the device-unique key. + * * A self-signed root certificate, signed by a dedicated key, certifying the + * intermediate. Ideally, the dedicated key would be the same for all StrongBox + * instances of the same manufacturer to ease validation. + * + * No additional chained certificates are provided. Only SecurityLevel::STRONGBOX + * IKeyMintDevices may support device-unique attestations. SecurityLevel::TRUSTED_ENVIRONMENT + * IKeyMintDevices must return ErrorCode::INVALID_ARGUMENT if they receive + * DEVICE_UNIQUE_ATTESTATION. + * SecurityLevel::STRONGBOX IKeyMintDevices need not support DEVICE_UNIQUE_ATTESTATION, and + * return ErrorCode::CANNOT_ATTEST_IDS if they do not support it. + * + * The caller needs to obtain the device-unique keys out-of-band and compare them against the + * key used to sign the self-signed root certificate. + * To ease this process, the IKeyMintDevice implementation should include, both in the subject + * and issuer fields of the self-signed root, the unique identifier of the device. Using the + * unique identifier will make it straightforward for the caller to link a device to its key. + * + * IKeyMintDevice implementations that support device-unique attestation MUST add the + * DEVICE_UNIQUE_ATTESTATION tag to device-unique attestations. + */ + DEVICE_UNIQUE_ATTESTATION = TagType.BOOL | 720, + + /** + * IDENTITY_CREDENTIAL_KEY is never used by IKeyMintDevice, is not a valid argument to key + * generation or any operation, is never returned by any method and is never used in a key + * attestation. It is used in attestations produced by the IIdentityCredential HAL when that + * HAL attests to Credential Keys. IIdentityCredential produces KeyMint-style attestations. + */ + IDENTITY_CREDENTIAL_KEY = TagType.BOOL | 721, + + /** + * To prevent keys from being compromised if an attacker acquires read access to system / kernel + * memory, some inline encryption hardware supports protecting storage encryption keys in + * hardware without software having access to or the ability to set the plaintext keys. + * Instead, software only sees wrapped version of these keys. + * + * STORAGE_KEY is used to denote that a key generated or imported is a key used for storage + * encryption. Keys of this type can either be generated or imported or secure imported using + * keyMint. The convertStorageKeyToEphemeral() method of IKeyMintDevice can be used to re-wrap + * storage key with a per-boot ephemeral key wrapped key once the key characteristics are + * enforced. + * + * Keys with this tag cannot be used for any operation within keyMint. + * ErrorCode::INVALID_OPERATION is returned when a key with Tag::STORAGE_KEY is provided to + * begin(). + */ + STORAGE_KEY = TagType.BOOL | 722, + + /** + * Tag::ATTESTATION_ID_SECOND_IMEI provides an additional IMEI of one of the radios on the + * device to attested key generation/import operations. It should be used to convey an + * IMEI different to the one conveyed by the Tag::ATTESTATION_ID_IMEI tag. Like all other + * ID attestation flags, it may be included independently of other tags. + * + * If the device does not support ID attestation (or destroyAttestationIds() was previously + * called and the device can no longer attest its IDs), any key attestation request that + * includes this tag must fail with ErrorCode::CANNOT_ATTEST_IDS. + * + * Must never appear in KeyCharacteristics. + */ + ATTESTATION_ID_SECOND_IMEI = TagType.BYTES | 723, + + /** + * Tag::MODULE_HASH specifies the SHA-256 hash of the DER-encoded module information (see + * KeyCreationResult.aidl for the ASN.1 schema). + * + * KeyStore clients can retrieve the unhashed DER-encoded module information from Android + * via KeyStoreManager.getSupplementaryAttestationInfo. + * + * This tag is never provided or returned from KeyMint in the key characteristics. It exists + * only to define the tag for use in the attestation record. + * + * Must never appear in KeyCharacteristics. + */ + MODULE_HASH = TagType.BYTES | 724, + + /** + * OBSOLETE: Do not use. + * + * This tag value is included for historical reasons -- in Keymaster it was used to hold + * associated data for AEAD encryption, as an additional parameter to + * IKeymasterDevice::finish(). In KeyMint the IKeyMintOperation::updateAad() method is used for + * this. + */ + ASSOCIATED_DATA = TagType.BYTES | 1000, + + /** + * Tag::NONCE is used to provide or return a nonce or Initialization Vector (IV) for AES-GCM, + * AES-CBC, AES-CTR, or 3DES-CBC encryption or decryption. This tag is provided to begin during + * encryption and decryption operations. It is only provided to begin if the key has + * Tag::CALLER_NONCE. If not provided, an appropriate nonce or IV must be randomly generated by + * KeyMint and returned from begin. + * + * The value is a blob, an arbitrary-length array of bytes. Allowed lengths depend on the mode: + * GCM nonces are 12 bytes in length; AES-CBC and AES-CTR IVs are 16 bytes in length, 3DES-CBC + * IVs are 8 bytes in length. + * + * Must never appear in KeyCharacteristics. + */ + NONCE = TagType.BYTES | 1001, + + /** + * Tag::MAC_LENGTH provides the requested length of a MAC or GCM authentication tag, in bits. + * + * The value is the MAC length in bits. It must be a multiple of 8 and at least as large as the + * value of Tag::MIN_MAC_LENGTH associated with the key. Otherwise, begin() must return + * ErrorCode::INVALID_MAC_LENGTH. + * + * Must never appear in KeyCharacteristics. + */ + MAC_LENGTH = TagType.UINT | 1003, + + /** + * Tag::RESET_SINCE_ID_ROTATION specifies whether the device has been factory reset since the + * last unique ID rotation. Used for key attestation. + * + * Must never appear in KeyCharacteristics. + */ + RESET_SINCE_ID_ROTATION = TagType.BOOL | 1004, + + /** + * OBSOLETE: Do not use. + * + * This tag value is included for historical reasons -- in Keymaster it was used to hold + * a confirmation token as an additional parameter to + * IKeymasterDevice::finish(). In KeyMint the IKeyMintOperation::finish() method includes + * a confirmationToken argument for this. + */ + CONFIRMATION_TOKEN = TagType.BYTES | 1005, + + /** + * Tag::CERTIFICATE_SERIAL specifies the serial number to be assigned to the attestation + * certificate to be generated for the given key. This parameter should only be passed to + * keyMint in the attestation parameters during generateKey() and importKey(). If not provided, + * the serial shall default to 1. + */ + CERTIFICATE_SERIAL = TagType.BIGNUM | 1006, + + /** + * Tag::CERTIFICATE_SUBJECT the certificate subject. The value is a DER encoded X509 NAME. + * This value is used when generating a self signed certificates. This tag may be specified + * during generateKey and importKey. If not provided the subject name shall default to + * CN="Android Keystore Key". + */ + CERTIFICATE_SUBJECT = TagType.BYTES | 1007, + + /** + * Tag::CERTIFICATE_NOT_BEFORE the beginning of the validity of the certificate in UNIX epoch + * time in milliseconds. This value is used when generating attestation or self signed + * certificates. ErrorCode::MISSING_NOT_BEFORE must be returned if this tag is not provided if + * this tag is not provided to generateKey or importKey. For importWrappedKey, there is no way + * to specify the value of this tag for a wrapped asymmetric key, so a value of 0 is suggested + * for certificate generation. + */ + CERTIFICATE_NOT_BEFORE = TagType.DATE | 1008, + + /** + * Tag::CERTIFICATE_NOT_AFTER the end of the validity of the certificate in UNIX epoch time in + * milliseconds. This value is used when generating attestation or self signed certificates. + * ErrorCode::MISSING_NOT_AFTER must be returned if this tag is not provided to generateKey or + * importKey. For importWrappedKey, there is no way to specify the value of this tag for a + * wrapped asymmetric key, so a value of 253402300799000 is suggested for certificate + * generation. + */ + CERTIFICATE_NOT_AFTER = TagType.DATE | 1009, + + /** + * Tag::MAX_BOOT_LEVEL specifies a maximum boot level at which a key should function. + * + * Over the course of the init process, the boot level will be raised to + * monotonically increasing integer values. Implementations MUST NOT allow the key + * to be used once the boot level advances beyond the value of this tag. + * + * Cannot be hardware enforced in this version. + */ + MAX_BOOT_LEVEL = TagType.UINT | 1010, +} diff --git a/libs/rust/aidl/android/hardware/security/keymint/TagType.aidl b/libs/rust/aidl/android/hardware/security/keymint/TagType.aidl new file mode 100644 index 0000000..d46e504 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/keymint/TagType.aidl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.keymint; + +/** + * TagType classifies Tags in Tag.aidl into various groups of data. + * @hide + */ +@VintfStability +@Backing(type="int") +enum TagType { + /** Invalid type, used to designate a tag as uninitialized. */ + INVALID = 0 << 28, + /** Enumeration value. */ + ENUM = 1 << 28, + /** Repeatable enumeration value. */ + ENUM_REP = 2 << 28, + /** 32-bit unsigned integer. */ + UINT = 3 << 28, + /** Repeatable 32-bit unsigned integer. */ + UINT_REP = 4 << 28, + /** 64-bit unsigned integer. */ + ULONG = 5 << 28, + /** 64-bit unsigned integer representing a date and time, in milliseconds since 1 Jan 1970. */ + DATE = 6 << 28, + /** Boolean. If a tag with this type is present, the value is "true". If absent, "false". */ + BOOL = 7 << 28, + /** + * Byte string containing an arbitrary-length integer, in a two's-complement big-endian + * ordering. The byte array contains the minimum number of bytes needed to represent the + * integer, including at least one sign bit (so zero encodes as the single byte 0x00. This + * matches the encoding of both java.math.BigInteger.toByteArray() and contents octets for an + * ASN.1 INTEGER value (X.690 section 8.3). Examples: + * - value 65536 encodes as 0x01 0x00 0x00 + * - value 65535 encodes as 0x00 0xFF 0xFF + * - value 255 encodes as 0x00 0xFF + * - value 1 encodes as 0x01 + * - value 0 encodes as 0x00 + * - value -1 encodes as 0xFF + * - value -255 encodes as 0xFF 0x01 + * - value -256 encodes as 0xFF 0x00 + */ + BIGNUM = 8 << 28, + /** Byte string */ + BYTES = 9 << 28, + /** Repeatable 64-bit unsigned integer */ + ULONG_REP = 10 << 28, +} diff --git a/libs/rust/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/libs/rust/aidl/android/hardware/security/secureclock/ISecureClock.aidl new file mode 100644 index 0000000..e6d63c8 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/secureclock/ISecureClock.aidl @@ -0,0 +1,52 @@ +/* + * 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. + * limitations under the License. + */ + +package android.hardware.security.secureclock; +import android.hardware.security.secureclock.TimeStampToken; + +/** + * Secure Clock definition. + * + * An ISecureClock provides a keymint service to generate secure timestamp using a secure platform. + * The secure time stamp contains time in milliseconds. This time stamp also contains a 256-bit MAC + * which provides integrity protection. The MAC is generated using HMAC-SHA-256 and a shared + * secret. The shared secret must be available to secure clock service by implementing + * ISharedSecret aidl. Note: ISecureClock depends on the shared secret, without which the secure + * time stamp token cannot be generated. + * + * The timer must be the same that is used for HardwareAuthTokens. The ISecureClock interface is + * used to convey a fresh timestamp to those components that do not share a timer with the + * authenticators. + * @hide + */ +@VintfStability +interface ISecureClock { + /** + * String used as context in the HMAC computation signing the generated time stamp. + * See TimeStampToken.mac for details. + */ + const String TIME_STAMP_MAC_LABEL = "Auth Verification"; + + /** + * Generates an authenticated timestamp. + * + * @param A challenge value provided by the relying party. It will be included in the generated + * TimeStampToken to ensure freshness. The relying service must ensure that the + * challenge cannot be specified or predicted by an attacker. + * + * @return the TimeStampToken, see the definition for details. + */ + TimeStampToken generateTimeStamp(in long challenge); +} diff --git a/libs/rust/aidl/android/hardware/security/secureclock/TimeStampToken.aidl b/libs/rust/aidl/android/hardware/security/secureclock/TimeStampToken.aidl new file mode 100644 index 0000000..fcf2ee8 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/secureclock/TimeStampToken.aidl @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.secureclock; + +import android.hardware.security.secureclock.Timestamp; + +/** + * TimeStampToken instances are used for secure environments that requires secure time information. + * @hide + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable TimeStampToken { + /** + * The challenge that was provided as argument to ISecureClock.generateTimeStamp by the client. + */ + long challenge; + + /** + * The current time of the secure environment that generates the TimeStampToken. + */ + Timestamp timestamp; + + /** + * 32-byte HMAC-SHA256 of the above values, computed as: + * + * HMAC(H, + * ISecureClock.TIME_STAMP_MAC_LABEL || challenge || timestamp || 1 ) + * + * where: + * + * ``ISecureClock.TIME_STAMP_MAC_LABEL'' is a string constant defined in ISecureClock.aidl. + * + * ``H'' is the shared HMAC key (see computeSharedHmac() in ISharedSecret). + * + * ``||'' represents concatenation + * + * The representation of challenge and timestamp is as 64-bit unsigned integers in big-endian + * order. 1, above, is a 32-bit unsigned integer, also big-endian. + */ + byte[] mac; +} diff --git a/libs/rust/aidl/android/hardware/security/secureclock/Timestamp.aidl b/libs/rust/aidl/android/hardware/security/secureclock/Timestamp.aidl new file mode 100644 index 0000000..93e9077 --- /dev/null +++ b/libs/rust/aidl/android/hardware/security/secureclock/Timestamp.aidl @@ -0,0 +1,30 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.security.secureclock; + +/** + * Time in milliseconds since some arbitrary point in time. Time must be monotonically increasing, + * and a secure environment's notion of "current time" must not repeat until the Android device + * reboots, or until at least 50 million years have elapsed (note that this requirement is satisfied + * by setting the clock to zero during each boot, and then counting time accurately). + * @hide + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable Timestamp { + long milliSeconds = -1; +} diff --git a/libs/rust/aidl/android/system/keystore2/AuthenticatorSpec.aidl b/libs/rust/aidl/android/system/keystore2/AuthenticatorSpec.aidl new file mode 100644 index 0000000..44cb062 --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/AuthenticatorSpec.aidl @@ -0,0 +1,38 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.hardware.security.keymint.HardwareAuthenticatorType; + +/** + * The authenticator spec is used by `IKeystoreSecurityLevel::importWrappedKey` + * to specify the sid of each possible authenticator type, e.g., password or + * biometric authenticator, that the imported key may be bound to. + * @hide + */ +@VintfStability +parcelable AuthenticatorSpec { + /** + * The type of the authenticator in question. + */ + HardwareAuthenticatorType authenticatorType = HardwareAuthenticatorType.NONE; + /** + * The secure user id by which the given authenticator knows the + * user that a key should be bound to. + */ + long authenticatorId; +} diff --git a/libs/rust/aidl/android/system/keystore2/Authorization.aidl b/libs/rust/aidl/android/system/keystore2/Authorization.aidl new file mode 100644 index 0000000..eb0e1ae --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/Authorization.aidl @@ -0,0 +1,27 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.SecurityLevel; + +/** @hide */ +@VintfStability +parcelable Authorization { + SecurityLevel securityLevel = SecurityLevel.SOFTWARE; + KeyParameter keyParameter; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/system/keystore2/CreateOperationResponse.aidl b/libs/rust/aidl/android/system/keystore2/CreateOperationResponse.aidl new file mode 100644 index 0000000..570297c --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/CreateOperationResponse.aidl @@ -0,0 +1,49 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.system.keystore2.IKeystoreOperation; +import android.system.keystore2.KeyParameters; +import android.system.keystore2.OperationChallenge; + +/** + * This parcelable is returned by `IKeystoreSecurityLevel::createOperation`. + * @hide + */ +@VintfStability +parcelable CreateOperationResponse { + /** + * The binder representing the newly created operation. + */ + @nullable IKeystoreOperation iOperation; // Actual type is NonNull, but compiler doesn't like it. + /** + * A challenge associated with the newly created operation. If this field is set. + * it indicates that the operation has yet to be authorized by the user. + */ + @nullable OperationChallenge operationChallenge; + /** + * Optional parameters returned from the KeyMint operation. This may contain a nonce + * or an initialization vector IV for operations that use them. + */ + @nullable KeyParameters parameters; + /** + * An optional opaque blob. If the key given to ISecurityLevel::CreateOperation + * uses Domain::BLOB and was upgraded, then this field is present, and represents the + * upgraded version of that key. + */ + @nullable byte[] upgradedBlob; +} diff --git a/libs/rust/aidl/android/system/keystore2/Domain.aidl b/libs/rust/aidl/android/system/keystore2/Domain.aidl new file mode 100644 index 0000000..cfe5bb1 --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/Domain.aidl @@ -0,0 +1,28 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +/** @hide */ +@VintfStability +@Backing(type="int") +enum Domain { + APP = 0, + GRANT = 1, + SELINUX = 2, + BLOB = 3, + KEY_ID = 4, +} diff --git a/libs/rust/aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl b/libs/rust/aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl new file mode 100644 index 0000000..91ce4d0 --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl @@ -0,0 +1,35 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +/** + * Includes the ephemeral storage key and an optional upgraded key blob if the storage key + * needed an upgrade. + * @hide + */ +@VintfStability +parcelable EphemeralStorageKeyResponse { + /** + * The ephemeral storage key. + */ + byte[] ephemeralKey; + /** + * An optional opaque blob. If the key given to ISecurityLevel::convertStorageKeyToEphemeral + * was upgraded, then this field is present, and represents the upgraded version of that key. + */ + @nullable byte[] upgradedBlob; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/system/keystore2/IKeystoreOperation.aidl b/libs/rust/aidl/android/system/keystore2/IKeystoreOperation.aidl new file mode 100644 index 0000000..559f92d --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/IKeystoreOperation.aidl @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +/** + * `IKeystoreOperation` represents a cryptographic operation with a Keystore key. + * + * The lifecycle of an operation begins with `KeystoreSecurityLevel.create`. + * It ends with a call to `finish`, `abort`, or when the reference to the binder is + * dropped. + * During the lifecycle of an operation `update` may be called multiple times. + * For AEAD operation `updateAad` may be called to add associated data, but + * it must be called before the first call to `update`. + * + * ## Error conditions + * Error conditions are reported as service specific error. + * Positive codes correspond to `android.system.keystore2.ResponseCode` + * and indicate error conditions diagnosed by the Keystore 2.0 service. + * Negative codes correspond to `android.hardware.security.keymint.ErrorCode` and + * indicate KeyMint back end errors. Refer to the KeyMint interface spec for + * detail. + * @hide + */ +@VintfStability +@SensitiveData +interface IKeystoreOperation { + + /** + * `updateAad` advances an operation by adding additional authenticated data (AAD also AD for + * associated data) to AEAD mode encryption or decryption operations. It cannot be called + * after `update`, and doing so will yield `ErrorCode.INVALID_TAG`. This error code + * is chosen for historic reasons, when AAD was passed as additional `KeyParameter` + * with tag `ASSOCIATED_DATA`. + * + * ## Error conditions + * * `ResponseCode::TOO_MUCH_DATA` if `aadInput` exceeds 32KiB. + * * `ResponseCode::OPERATION_BUSY` if `updateAad` is called concurrently with any other + * `IKeystoreOperation` API call. + * * `ErrorCode.INVALID_TAG` if `updateAad` was called after `update` on a given operation. + * TODO: this error code may change before stabilization/freezing of the API. + * * `ErrorCode.INVALID_OPERATION_HANDLE` if the operation finalized for any reason. + * + * Note: Any error condition except `ResponseCode::OPERATION_BUSY` finalizes the + * operation such that subsequent API calls will yield `INVALID_OPERATION_HANDLE`. + * + * @param aadInput The AAD to be added to the operation. + * + */ + void updateAad(in byte[] aadInput); + + /** + * Update advances an operation by adding more data to operation. Input data + * may be to-be-encrypted or to-be-signed plain text, or to-be-decrypted + * cipher text. During encryption operation this function returns the resulting + * cipher text, and during decryption operations this function returns the + * resulting plain text. Nothing it return during signing. + * + * ## Error conditions + * * `ResponseCode::TOO_MUCH_DATA` if `input` exceeds 32KiB. + * * `ResponseCode::OPERATION_BUSY` if `updateAad` is called concurrently with any other + * `IKeystoreOperation` API call. + * * `ErrorCode.INVALID_OPERATION_HANDLE` if the operation finalized for any reason. + * + * Note: Any error condition except `ResponseCode::OPERATION_BUSY` finalizes the + * operation such that subsequent API calls will yield `INVALID_OPERATION_HANDLE`. + * + * @param input Input data. + * + * @return Optional output data. + */ + @nullable byte[] update(in byte[] input); + + /** + * Finalizes the operation. `finish` takes a final chunk of input data like `update`. + * The output may be a signature during signing operations or plain text or cypher text + * during encryption or decryption operations respectively. + * + * ## Error conditions + * * `ResponseCode::TOO_MUCH_DATA` if `input` exceeds 32KiB. + * * `ResponseCode::OPERATION_BUSY` if `updateAad` is called concurrently with any other + * `IKeystoreOperation` API call. + * * `ErrorCode.INVALID_OPERATION_HANDLE` if the operation finalized for any reason. + * + * Note: `finish` finalizes the operation regardless of the outcome unless + * `ResponseCode::OPERATION_BUSY` was returned. + * + * @param input Finish takes one last chunk of data. + * + * @param signature An optional HMAC signature if the operation is an HMAC verification. + * TODO @swillden is this field used for anything else, if we don't do + * public key operations any more? + * + * @return A signature when finalizing a signing operation, or an AEAD message tag when + * performing an authenticated encryption, or the final chunk of cipher + * or plain text during encryption or decryption respectively. + */ + @nullable byte[] finish(in @nullable byte[] input, in @nullable byte[] signature); + + /** + * Aborts the operation. + * + * Note: `abort` finalizes the operation regardless of the outcome unless + * `ResponseCode::OPERATION_BUSY` was returned. + */ + void abort(); +} diff --git a/libs/rust/aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl b/libs/rust/aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl new file mode 100644 index 0000000..7aa422a --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.hardware.security.keymint.KeyParameter; +import android.system.keystore2.AuthenticatorSpec; +import android.system.keystore2.CreateOperationResponse; +import android.system.keystore2.EphemeralStorageKeyResponse; +import android.system.keystore2.IKeystoreOperation; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; + +/** + * `IKeystoreSecurityLevel` is the per backend interface to Keystore. It provides + * access to all requests that require KeyMint interaction, such as key import + * and generation, as well as cryptographic operations. + * + * ## Error conditions + * Error conditions are reported as service specific error. + * Positive codes correspond to `android.system.keystore2.ResponseCode` + * and indicate error conditions diagnosed by the Keystore 2.0 service. + * Negative codes correspond to `android.hardware.security.keymint.ErrorCode` and + * indicate KeyMint back end errors. Refer to the KeyMint interface spec for + * detail. + * @hide + */ +@VintfStability +@SensitiveData +interface IKeystoreSecurityLevel { + + /** + * This flag disables cryptographic binding to the LSKF for auth bound keys. + * It has no effect non auth bound keys. Such keys are not bound to the LSKF by + * default. + */ + const int KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING = 0x1; + + /** + * This function creates a new key operation. Operations are the mechanism by which the + * secret or private key material of a key can be used. There is a limited number + * of operation slots. Implementations may prune an existing operation to make room + * for a new one. The pruning strategy is implementation defined, but it must + * account for forced operations (see parameter `forced` below). + * Forced operations require the caller to possess the `REQ_FORCED_OP` permission. + * + * ## Pruning strategy recommendation + * It is recommended to choose a strategy that rewards "good" behavior. + * It is considered good behavior not to hog operations. Clients that use + * few parallel operations shall have a better chance of starting and finishing + * an operations than those that use many. Clients that use frequently update their + * operations shall have a better chance to complete them successfully that those + * that let their operations linger. + * + * ## Error conditions + * `ResponseCode::BACKEND_BUSY` if the implementation was unable to find a free + * or free up an operation slot for the new operation. + * + * @param key Describes the key that is to be used for the operation. + * + * @param operationParameters Additional operation parameters that describe the nature + * of the requested operation. + * + * @param forced A forced operation has a very high pruning power. The implementation may + * select an operation to be pruned that would not have been pruned otherwise to + * free up an operation slot for the caller. Also, the resulting operation shall + * have a very high pruning resistance and cannot be pruned even by other forced + * operations. + * + * @return The operation interface which also acts as a handle to the pending + * operation and an optional operation challenge wrapped into the + * `CreateOperationResponse` parcelable. If the latter is present, user + * authorization is required for this operation. + */ + CreateOperationResponse createOperation(in KeyDescriptor key, + in KeyParameter[] operationParameters, in boolean forced); + + /** + * Generates a new key and associates it with the given descriptor. + * + * ## Error conditions + * `ResponseCode::INVALID_ARGUMENT` if `key.domain` is set to any other value than + * the ones described above. + * A KeyMint ErrorCode may be returned indicating a backend diagnosed error. + * + * @param key The domain field of the key descriptor governs how the key will be stored. + * * App: The key is stored by the given alias string in the implicit UID namespace + * of the caller. + * * SeLinux: The key is stored by the alias string in the namespace given by the + * `nspace` field provided the caller has the appropriate access rights. + * * Blob: The key is returned as an opaque KeyMint blob in the KeyMetadata.key.blob + * field of the return value. + * The `alias` field is ignored. The caller must have the `MANAGE_BLOB` + * permission for the targeted `keystore2_key` context given by + * `nspace`. `nspace` is translated into the corresponding target context + * `` and `:keystore2_key manage_blob` is + * checked against the caller's context. + * + * @param attestationKey Optional key to be used for signing the attestation certificate. + * + * @param params Describes the characteristics of the to be generated key. See KeyMint HAL + * for details. + * + * @param flags Additional flags that influence the key generation. + * See `KEY_FLAG_*` constants above for details. + * + * @param entropy This array of random bytes is mixed into the entropy source used for key + * generation. + * + * @return KeyMetadata includes: + * * A key descriptor that can be used for subsequent key operations. + * If `Domain::BLOB` was requested, then the descriptor contains the + * generated key, and the caller must assure that the key is persistently + * stored accordingly; there is no way to recover the key if the blob is + * lost. + * * The generated public certificate if applicable. If `Domain::BLOB` was + * requested, there is no other copy of this certificate. It is the caller's + * responsibility to store it persistently if required. + * * The generated certificate chain if applicable. If `Domain::BLOB` was + * requested, there is no other copy of this certificate chain. It is the + * caller's responsibility to store it persistently if required. + * * The `IKeystoreSecurityLevel` field is always null in this context. + */ + KeyMetadata generateKey(in KeyDescriptor key, in @nullable KeyDescriptor attestationKey, + in KeyParameter[] params, in int flags, in byte[] entropy); + + + /** + * Imports the given key. This API call works exactly like `generateKey`, only that the key is + * provided by the caller rather than being generated by KeyMint. We only describe + * the parameters where they deviate from the ones of `generateKey`. + * + * @param keyData The key to be imported. Expected encoding is PKCS#8 for asymmetric keys and + * raw key bits for symmetric keys. + * + * @return KeyMetadata see `generateKey`. + */ + KeyMetadata importKey(in KeyDescriptor key, in @nullable KeyDescriptor attestationKey, + in KeyParameter[] params, in int flags, in byte[] keyData); + + /** + * Allows importing keys wrapped with an RSA encryption key that is stored in AndroidKeystore. + * + * ## Error conditions + * `ResponseCode::KEY_NOT_FOUND` if the specified wrapping key did not exist. + * + * @param key Governs how the imported key shall be stored. See `generateKey` for details. + * + * @param wrappingKey Indicates the key that shall be used for unwrapping the wrapped key + * in a manner similar to starting a new operation with create. + * + * @param maskingKey Reserved for future use. Must be null for now. + * + * @param params These parameters describe the cryptographic operation that shall be performed + * using the wrapping key in order to unwrap the wrapped key. + * + * @param authenticators When generating or importing a key that is bound to a specific + * authenticator, the authenticator ID is included in the key parameters. + * Imported wrapped keys can also be authentication bound, however, the + * key parameters were included in the wrapped key at a remote location + * where the device's authenticator ID is not known. Therefore, the + * caller has to provide all of the possible authenticator IDs so that + * KeyMint can pick the right one based on the included key parameters. + * + * @return KeyMetadata see `generateKey`. + */ + KeyMetadata importWrappedKey(in KeyDescriptor key, in KeyDescriptor wrappingKey, + in @nullable byte[] maskingKey, in KeyParameter[] params, + in AuthenticatorSpec[] authenticators); + + /** + * Allows getting a per-boot wrapped ephemeral key from a wrapped storage key. + * + * ## Error conditions + * `ResponseCode::PERMISSION_DENIED` if the caller does not have the + * `ConvertStorageKeyToEphemeral` or the `ManageBlob` keystore2_key permissions + * `ResponseCode::INVALID_ARGUMENT` if key.domain != Domain::BLOB or a key.blob isn't specified. + * + * A KeyMint ErrorCode may be returned indicating a backend diagnosed error. + * + * @param storageKey The KeyDescriptor with domain Domain::BLOB, and keyblob representing + * the input wrapped storage key to convert + * + * @return byte[] representing the wrapped per-boot ephemeral key and an optional upgraded + * key blob. + */ + EphemeralStorageKeyResponse convertStorageKeyToEphemeral(in KeyDescriptor storageKey); + + /** + * Allows deleting a Domain::BLOB key from the backend underlying this IKeystoreSecurityLevel. + * While there's another function "deleteKey()" in IKeystoreService, that function doesn't + * handle Domain::BLOB keys because it doesn't have any information about which underlying + * device to actually delete the key blob from. + * + * ## Error conditions + * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permission `DELETE` + * for the designated key, or the "MANAGE_BLOB" permission to manage + * Domain::BLOB keys. + * `ResponseCode::INVALID_ARGUMENT` if key.domain != Domain::BLOB or key.blob isn't specified. + * + * A KeyMint ErrorCode may be returned indicating a backend diagnosed error. + * + * @param KeyDescriptor representing the key to delete. + */ + void deleteKey(in KeyDescriptor key); +} diff --git a/libs/rust/aidl/android/system/keystore2/IKeystoreService.aidl b/libs/rust/aidl/android/system/keystore2/IKeystoreService.aidl new file mode 100644 index 0000000..886047d --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/IKeystoreService.aidl @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.hardware.security.keymint.SecurityLevel; +import android.hardware.security.keymint.Tag; +import android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; + +/** + * `IKeystoreService` is the primary interface to Keystore. It primarily provides + * access to simple database bound requests. Request that require interactions + * with a KeyMint backend are delegated to `IKeystoreSecurityLevel` which + * may be acquired through this interface as well. + * + * ## Error conditions + * Error conditions are reported as service specific error. + * Positive codes correspond to `android.system.keystore2.ResponseCode` + * and indicate error conditions diagnosed by the Keystore 2.0 service. + * Negative codes correspond to `android.hardware.security.keymint.ErrorCode` and + * indicate KeyMint back end errors. Refer to the KeyMint interface spec for + * detail. + * @hide + */ +@VintfStability +interface IKeystoreService { + + /** + * Returns the security level specific interface. + * + * ## Error conditions + * `ErrorCode::HARDWARE_TYPE_UNAVAILABLE` if the requested security level does not exist. + */ + IKeystoreSecurityLevel getSecurityLevel(in SecurityLevel securityLevel); + + /** + * Loads all relevant information about a key stored in the keystore database. + * This includes the key metadata describing the key algorithm and usage + * restrictions, and an optional certificate and certificate chain if + * it is an asymmetric key. + * + * The metadata also includes an updated key id based KeyDescriptor (Domain::KEY_ID). + * See KeyDescriptor.aidl for benefits of key id based key descriptor usage. + * + * The key maybe specified by any type of key descriptor (see KeyDescriptor.aidl) + * except `Domain::BLOB`, in which case the key is not stored in the database. + * + * ## Error conditions + * `ResponseCode::KEY_NOT_FOUND` if the key did not exist. + * `ResponseCode::PERMISSION_DENIED` if the caller does not possess the `GET_INFO` permission + * for the specified key. + * + * @param key Describes the key entry that is to be loaded. + * + * @param metadata Returns the key characteristics, security level of the key and + * key id based key descriptor. + * + * @param publicCert The public certificate if the requested key is an asymmetric key. + * + * @param certificateChain The certificate chain if the key has one, e.g., if the + * key was generated with attestation or if the client + * installed one using `updateSubcomponent`. + * + * @return The `KeyEntryResponse includes the requested key's metadata and the security level + * interface corresponding to the key's security level, which can be used + * to start operations, generate, and import keys. + */ + KeyEntryResponse getKeyEntry(in KeyDescriptor key); + + /** + * Allows setting the public key or certificate chain of an asymmetric key. + * Keystore 2.0 populates the subcomponents of asymmetric keys with a self signed certificate + * or an attestation certificate, and an optional certificate chain. With this function these + * fields can be updated with custom certificates. + * + * Callers require the `UPDATE` permission. + * + * If no key by the given name is found and only a the certificateChain argument is given, + * A new entry is created by the given name. This is used by the Keystore SPI to create. + * pure certificate entries. In this case the `REBIND` permission is checked. + * + * ## Error conditions + * `ResponseCode::KEY_NOT_FOUND` if the key did not exist. + * `ResponseCode::PERMISSION_DENIED` if the caller does not possess the `UPDATE` permission + * for the specified key. + * + * @param key The key the subcomponents of which are to be updated. + * + * @param publicCert An optional new public key certificate for the given key. + * + * @param certificateChain An optional certificate chain for the given key. + */ + void updateSubcomponent(in KeyDescriptor key, in @nullable byte[] publicCert, + in @nullable byte[] certificateChain); + + /** + * List all entries accessible by the caller in the given `domain` and `nspace`. + * If the number of entries accessible by the caller is greater than could fit in one Binder + * transaction, a truncated list may be returned. Use `listEntriesBatched` in this case to + * list all entries in batches. + * + * Callers must have the `GET_INFO` permission for the requested namespace to list all the + * entries. + * + * ## Error conditions + * `ResponseCode::INVALID_ARGUMENT` if `domain` is other than `Domain::APP` or `Domain::SELINUX` + * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permission `GET_INFO` + * For the requested namespace. + * + * @param domain If `Domain::APP` is passed, returns all keys associated with the caller's UID and + * the namespace parameter is ignored. + * If `Domain::SELINUX` is passed, returns all keys associated with the given + * namespace. + * + * @param nspace The SELinux keystore2_key namespace if `domain` is `Domain::SELINUX`, + * ignored otherwise. + * + * Note: `namespace` is a keyword in C++, the underscore disambiguates. + * + * @return List of KeyDescriptors. + * @deprecated use listEntriesBatched instead. + */ + KeyDescriptor[] listEntries(in Domain domain, in long nspace); + + /** + * Deletes the designated key. This method can be used on keys with any domain except + * Domain::BLOB, since keystore knows which security level any non Domain::BLOB key + * belongs to. To delete Domain::BLOB keys, use IKeystoreSecurityLevel::deleteKey() + * instead. + * + * ## Error conditions + * `ResponseCode::KEY_NOT_FOUND` if the key designated by `key` did not exist. + * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permission `DELETE` + * for the designated key. + * + * @param key The key to be deleted. + */ + void deleteKey(in KeyDescriptor key); + + /** + * Grant takes a key descriptor `key`, the uid of a grantee `granteeUid` and a set of + * permissions `accessVector`. + * It creates a new key descriptor `grantKey` that can be used by the grantee designated by + * its uid. Before the grantee can use the new grant, `grantKey` must be communicated to the + * grantee using IPC. + * Key usage by the grantee is restricted by `accessVector` which is a set of permissions of the + * SELinux class "keystore2_key". A grant can never convey more privileges to a key than the + * owner of that key has. To that end, this function must check if `accessVector` is a subset + * of the granter's permission to their key. Additionally, the "grant" permission is checked. + * If either of these checks fails `ResponseCode::PERMISSION_DENIED` is returned. If the grant + * already exists, this call may update the access vector and return the same grant key + * descriptor as when the grant was first created. + * + * ## Error conditions + * `ResponseCode::KEY_NOT_FOUND` if the key designated by `key` did not exist. + * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permission `GRANT` + * for the designated key. + * + * @param key The key to be granted access to. + * + * @param granteeUid The UID of the grantee. + * + * @param accessVector A bitmap of `KeyPermission` values. + * + * @return A key descriptor that can be used by the grantee to perform operations + * on the given key within the limits of the supplied access vector. + */ + KeyDescriptor grant(in KeyDescriptor key, in int granteeUid, in int accessVector); + + /** + * Removes a grant from the grant database. Because a key can be granted to multiple parties, + * a grant is uniquely identified by target key and the grantee's UID. + * + * ## Error conditions + * `ResponseCode::KEY_NOT_FOUND` if the key designated by `key` did not exist. + * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permission `grant` + * for the designated key. + */ + void ungrant(in KeyDescriptor key, in int granteeUid); + + /** + * Get the number of entries accessible to the caller in the given `domain` and `nspace`. + * + * Callers must have the `GET_INFO` permission for the requested namespace determine the number + * of entries. + * + * ## Error conditions + * `ResponseCode::INVALID_ARGUMENT` if `domain` is other than `Domain::APP` or `Domain::SELINUX` + * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permission `GET_INFO` + * For the requested namespace. + * + * @param domain If `Domain::APP` is passed, returns all keys associated with the caller's UID + * and the namespace parameter is ignored. + * If `Domain::SELINUX` is passed, returns all keys associated with the given + * namespace. + * + * @param nspace The SELinux keystore2_key namespace if `domain` is `Domain::SELINUX`, + * ignored otherwise. + * + * @return Number of entries. + */ + int getNumberOfEntries(in Domain domain, in long nspace); + + /** + * List all entries accessible by the caller in the given `domain` and + * `nspace`, starting with the first entry greater than `startingPastAlias`. + * If the number of entries accessible by the caller is greater than could fit in one Binder + * transaction, a truncated list will be returned. + * + * See the `listEntries` variant above for calling permissions and documentation of the + * `domain` and `nspace` parameters. + * + * Notes: + * Consistency: The order of entries returned by this method is stable across calls. + * If entries have been deleted or added to Keystore between calls to + * this method, then some entries may be missing from the combined listing. + * + * Length of returned list: If Keystore estimates that the returned list would exceed + * the Binder transaction size limit, it will return a smaller number of entries than + * are available. Subsequent calls to this method need to be made with different + * starting points. + * + * @param domain See `listEntries` + * + * @param nspace See `listEntries` + * + * @param startingPastAlias Only return aliases lexicographically bigger than this value. + * + * @return List of KeyDescriptors. + */ + KeyDescriptor[] listEntriesBatched(in Domain domain, in long nspace, + in @nullable String startingPastAlias); + + /** + * Returns tag-specific info required to interpret a tag's attested value. + * Attested values themselves are located in the attestation certificate. + * + * The semantics of the return value is specific to the input tag: + * + * o Tag::MODULE_HASH: returns the DER-encoded structure corresponding to the `Modules` schema + * described in the KeyMint HAL's KeyCreationResult.aidl. The SHA-256 hash of this encoded + * structure is what's included with the tag in attestations. To ensure the returned encoded + * structure is the one attested to, clients should verify its SHA-256 hash matches the one + * in the attestation. Note that the returned structure can vary between boots. + * + * ## Error conditions + * `ResponseCode::INVALID_ARGUMENT` if `tag` is not specified in the list above. + * `ResponseCode::INFO_NOT_AVAILABLE` if `IKeystoreService` does not have the requested info. + */ + byte[] getSupplementaryAttestationInfo(in Tag tag); +} diff --git a/libs/rust/aidl/android/system/keystore2/KeyDescriptor.aidl b/libs/rust/aidl/android/system/keystore2/KeyDescriptor.aidl new file mode 100644 index 0000000..1f8d986 --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/KeyDescriptor.aidl @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.system.keystore2.Domain; + +/** + * The key descriptor designates a key stored in keystore in different ways. + * based on the given domain specifier. + * + * Domain::APP: The `nspace` field is ignored, and the caller's uid is used instead. + * The access tuple is `(App, caller_uid, alias)`. + * Domain::SELINUX: The `nspace` field is used. + * The access tuple is `(SELinux, nspace, alias)`. + * Domain::GRANT: The `nspace` field holds a grant id. The key_id is looked up + * in the grant database and the key is accessed by the key_id. + * Domain::KEY_ID: The `nspace` field holds the `key_id` which can be used + * to access the key directly. + * While alias based key descriptors can yield different keys every time they are + * used because aliases can be rebound to newly generated or imported keys, the key + * id is unique for a given key. Using a key by its key id in subsequent Keystore + * calls guarantees that the private/secret key material used corresponds to the + * metadata previously loaded using `loadKeyEntry`. The key id does not protect + * against rebinding, but if the corresponding alias was rebound the key id ceases + * to be valid, thereby, indicating to the caller that the previously loaded + * metadata and public key material no longer corresponds to the key entry. + * + * Note: Implementations must choose the key id as 64bit random number. So there is + * a minimal non-zero change of a collision with a previously existing key id. + * Domain::BLOB: The `blob` field holds the key blob. It is not in the database. + * + * The key descriptor is used by various API calls. In any case, the implementation + * must perform appropriate access control to assure that the caller has access + * to the given key for the given request. In case of `Domain::BLOB` the implementation + * must additionally check if the caller has `ManageBlob` permission. See KeyPermission.aidl. + * @hide + */ +@VintfStability +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true) +parcelable KeyDescriptor { + Domain domain = Domain.APP; + long nspace; /* namespace is a keyword in C++, so we had a to pick a different field name. */ + /** + * A free form string denoting the key, chosen by the client. + */ + @nullable String alias; + /** + * An opaque blob. This blob is represents a KeyMint key. It is encrypted + * and cannot be interpreted by the client. + */ + @nullable byte[] blob; +} diff --git a/libs/rust/aidl/android/system/keystore2/KeyEntryResponse.aidl b/libs/rust/aidl/android/system/keystore2/KeyEntryResponse.aidl new file mode 100644 index 0000000..56d0042 --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/KeyEntryResponse.aidl @@ -0,0 +1,43 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyMetadata; + +/** + * This includes the metadata of the requested key and the security level interface + * corresponding to the key's security level. + * @hide + */ +@VintfStability +parcelable KeyEntryResponse { + /** + * The security level interface is served by the Keystore instance + * that corresponds to the key's security level. It can be used to start + * operations, generate, and import keys. This field is optional, + * it is only populated by `IKeystoreService::getKeyEntry`. + */ + @nullable IKeystoreSecurityLevel iSecurityLevel; + /** + * The KeyId based key descriptor. Using this key descriptor for subsequent + * operations ensures that the private key material used in those operations + * corresponds to the meta data in this structure. Alias based key descriptors + * may point to a different key if the alias was rebound in the meantime. + */ + KeyMetadata metadata; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/system/keystore2/KeyMetadata.aidl b/libs/rust/aidl/android/system/keystore2/KeyMetadata.aidl new file mode 100644 index 0000000..df0aa3b --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/KeyMetadata.aidl @@ -0,0 +1,63 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.hardware.security.keymint.SecurityLevel; +import android.system.keystore2.Authorization; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; + +/** + * Metadata of a key entry including the key characteristics `authorizations` + * security level `securityLevel` and a key id based key descriptor. + * See KeyDescriptor.aidl for the benefits of key id based key descriptor usage. + * @hide + */ +@VintfStability +parcelable KeyMetadata { + /** + * The KeyId based key descriptor. Using this key descriptor for subsequent + * operations ensures that the private key material used in those operations + * corresponds to the meta data in this structure. Alias based key descriptors + * may point to a different key if the alias was rebound in the meantime. + */ + KeyDescriptor key; + /** + * The security level that the key resides in. + * TODO, we could also take this from the origin tag in authorizations. + */ + SecurityLevel keySecurityLevel = SecurityLevel.SOFTWARE; + /** + * The authorizations describing the key, e.g., the algorithm, key size, + * permissible purposes, digests and paddings, as well as usage restrictions, + * e.g., whether or not user authorization is required. + */ + Authorization[] authorizations; + /** + * The public certificate if the requested key is an asymmetric key. + */ + @nullable byte[] certificate; + /** + * The certificate chain if the key has one, e.g., if the key was generated with + * attestation or if the client installed one using `updateSubcomponent`. + */ + @nullable byte[] certificateChain; + /** + * The time of the last modification in milliseconds since January 1st 1970. + */ + long modificationTimeMs; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/system/keystore2/KeyParameters.aidl b/libs/rust/aidl/android/system/keystore2/KeyParameters.aidl new file mode 100644 index 0000000..8e3ff5e --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/KeyParameters.aidl @@ -0,0 +1,25 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +import android.hardware.security.keymint.KeyParameter; + +/** @hide */ +@VintfStability +parcelable KeyParameters { + KeyParameter[] keyParameter; +} diff --git a/libs/rust/aidl/android/system/keystore2/KeyPermission.aidl b/libs/rust/aidl/android/system/keystore2/KeyPermission.aidl new file mode 100644 index 0000000..2f623fb --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/KeyPermission.aidl @@ -0,0 +1,106 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +/** + * KeyPermissions correspond to SELinux permission for the keystore2_key security class. + * These values are used to specify the access vector for granted keys. + * Key owners may grant a key to a another protection domain indicated by their uid. + * Implementations must only allow granting permissions that the key owner possesses. + * The grant permission cannot be granted. + * Any granted permission applies only to the granted key. + * @hide + */ +@VintfStability +@Backing(type="int") +enum KeyPermission { + /** + * Convenience variant indicating an empty access vector. + */ + NONE = 0, + /** + * Allows deleting the key. + */ + DELETE = 0x1, + /** + * Allows the usage of UNIQUE ID with the given key. + */ + GEN_UNIQUE_ID = 0x2, + /** + * Allows reading metadata about the key including public key and certificate. + */ + GET_INFO = 0x4, + /** + * Allows granting the key. Implementations must not allow this permission + * to be granted though, so this is mentioned here just for completeness. + */ + GRANT = 0x8, + /** + * Allows using a key by specifying the key blob in the key descriptor. + * Implementations must not allow this permission to be granted. + */ + MANAGE_BLOB = 0x10, + /** + * Allows rebinding an alias to a newly imported or generated key. + * It makes no sense to grant this permission, because the API does not + * permit generating keys by domains other than `App` or `SELinux`. + * Implementations must not allow this permission to be granted. + */ + REBIND = 0x20, + /** + * Allow requesting a forced operation with the given key. + * Forced operations cannot be pruned and they have special pruning power + * allowing them to evict any non forced operation to obtain a KeyMint + * operation slot. + */ + REQ_FORCED_OP = 0x40, + /** + * Allow updating the public certificate and certificate chain fields + * of the given key. + */ + UPDATE = 0x80, + /** + * Allow using the key for cryptographic operations within the limitations + * of the key's usage restrictions. + */ + USE = 0x100, + /** + * DO NOT USE. DOES NOTHING. + * To generate a key with hardware identifiers for device ID attestation, + * the caller must hold the READ_PRIVILEGED_PHONE_STATE Android permission. + * Additionally, note that it makes no sense to grant this permission, + * because attestation only works during key generation, and keys cannot be + * created through a grant. Implementations must not allow this permission + * to be granted. + */ + USE_DEV_ID = 0x200, + /** + * Allows the creation of auth bound keys that are not cryptographically bound to the LSKF. + * System components might need this if they required a logically authentication bound key + * that is used for the derivation of the LSKF bound key material. This is essentially breaking + * up a circular dependency. + * This permission is checked during key generation and import if the + * `KeyFlag.AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING` was set. Because keys cannot + * be generated or imported via grant, it does not make sense to grant this key. + */ + USE_NO_LSKF_BINDING = 0x400, + /** + * Allows getting a per-boot wrapped ephemeral key from a long lived wrapped storage key. + * This permission is checked on calls to IKeystoreSecurityLevel::convertStorageKeyToEphemeral() + */ + CONVERT_STORAGE_KEY_TO_EPHEMERAL = 0x800, + } diff --git a/libs/rust/aidl/android/system/keystore2/OperationChallenge.aidl b/libs/rust/aidl/android/system/keystore2/OperationChallenge.aidl new file mode 100644 index 0000000..bf8638b --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/OperationChallenge.aidl @@ -0,0 +1,28 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +/** + * An operation challenge is returned by `IKeystoreSecurityLevel::create` iff + * the new operation requires user authorization. The challenge may be passed + * to an authenticator, such as Gatekeeper or Fingerprint. + * @hide + */ +@VintfStability +parcelable OperationChallenge { + long challenge; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/system/keystore2/ResponseCode.aidl b/libs/rust/aidl/android/system/keystore2/ResponseCode.aidl new file mode 100644 index 0000000..0424f5b --- /dev/null +++ b/libs/rust/aidl/android/system/keystore2/ResponseCode.aidl @@ -0,0 +1,144 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.system.keystore2; + +/** @hide */ +@VintfStability +@Backing(type="int") +enum ResponseCode { + /* 1 Reserved - formerly NO_ERROR */ + /** + * TODO Determine exact semantic of Locked and Uninitialized. + */ + LOCKED = 2, + UNINITIALIZED = 3, + /** + * Any unexpected Error such as IO or communication errors. + * Implementations should log details to logcat. + */ + SYSTEM_ERROR = 4, + /* 5 Reserved - formerly "protocol error" was never used */ + /** + * Indicates that the caller does not have the permissions for the attempted request. + */ + PERMISSION_DENIED = 6, + /** + * Indicates that the requested key does not exist. + */ + KEY_NOT_FOUND = 7, + /** + * Indicates a data corruption in the Keystore 2.0 database. + */ + VALUE_CORRUPTED = 8, + /* + * 9 Reserved - formerly "undefined action" was never used + * 10 Reserved - formerly WrongPassword + * 11 - 13 Reserved - formerly password retry count indicators: obsolete + * + * 14 Reserved - formerly SIGNATURE_INVALID: Keystore does not perform public key operations + * any more + * + * + * 15 Reserved - Formerly OP_AUTH_NEEDED. This is now indicated by the optional + * `OperationChallenge` returned by `IKeystoreSecurityLevel::create`. + * + * 16 Reserved + */ + KEY_PERMANENTLY_INVALIDATED = 17, + /** + * May be returned by `IKeystoreSecurityLevel.create` if all Keymint operation slots + * are currently in use and none can be pruned. + */ + BACKEND_BUSY = 18, + /** + * This is a logical error on the caller's side. They are trying to advance an + * operation, e.g., by calling `update`, that is currently processing an `update` + * or `finish` request. + */ + OPERATION_BUSY = 19, + /** + * Indicates that an invalid argument was passed to an API call. + */ + INVALID_ARGUMENT = 20, + /** + * Indicates that too much data was sent in a single transaction. + * The binder kernel mechanism cannot really diagnose this condition unambiguously. + * So in order to force benign clients into reasonable limits, we limit the maximum + * amount of data that we except in a single transaction to 32KiB. + */ + TOO_MUCH_DATA = 21, + + /** + * Deprecated in API 34. + * Previous to API 34, all failures to generate a key due to the exhaustion of the + * remotely provisioned key pool were reflected as OUT_OF_KEYS. The client simply had + * to retry. Starting with API 34, more detailed errors have been included so that + * applications can take appropriate action. + * @deprecated replaced by other OUT_OF_KEYS_* errors below + */ + OUT_OF_KEYS = 22, + + /** + * This device needs a software update as it may contain potentially vulnerable software. + * This error is returned only on devices that rely solely on remotely-provisioned keys (see + * Remote Key Provisioning). + */ + OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE = 23, + + /** + * Indicates that the attestation key pool has been exhausted, and the remote key + * provisioning server cannot currently be reached. Clients should wait for the + * device to have connectivity, then retry. + */ + OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY = 24, + + /** + * Indicates that the attestation key pool temporarily does not have any signed + * attestation keys available. This can be thrown during attempts to generate a key. + * This error indicates key generation may be retried with exponential back-off, as + * future attempts to fetch attestation keys are expected to succeed. + * + * NOTE: This error is generally the last resort of the underlying provisioner. Future + * OS updates should strongly consider adding new error codes if/when appropriate rather + * than relying on this status code as a fallback. + */ + OUT_OF_KEYS_TRANSIENT_ERROR = 25, + + /** + * Indicates that this device will never be able to provision attestation keys using + * the remote provisioning server. This may be due to multiple causes, such as the + * device is not registered with the remote provisioning backend or the device has + * been permanently revoked. Clients who receive this error should not attempt to + * retry key creation. + */ + OUT_OF_KEYS_PERMANENT_ERROR = 26, + + /** + * Indicates that the device had an error when getting the attestation application + * id. This is a temporary error that can be retried. This can happen if there is a + * failure to make a binder call to the package manager from Keystore service. + * The attestation can be retried as this can be seen as a warning. + */ + GET_ATTESTATION_APPLICATION_ID_FAILED = 27, + + /** + * Indicates that some information is not available. + */ + INFO_NOT_AVAILABLE = 28, +} diff --git a/libs/rust/build.rs b/libs/rust/build.rs index 5025cc6..5ad8f91 100644 --- a/libs/rust/build.rs +++ b/libs/rust/build.rs @@ -1,4 +1,4 @@ -use std::fs; +use std::{fs, path::PathBuf, vec}; fn main() { let out_dir = "src/proto"; @@ -19,6 +19,43 @@ fn main() { .map(|file| format!("pub mod {};", file.trim_end_matches(".rs"))) .collect::>() .join("\n"); - + std::fs::write(&mod_file, mod_content).unwrap(); + + let mut aidl = rsbinder_aidl::Builder::new() + .include_dir(PathBuf::from("aidl/android/system/keystore2")) + .include_dir(PathBuf::from("aidl/android/hardware/security/keymint")) + .output(PathBuf::from("aidl.rs")); + + let dirs = vec![ + "aidl/android/system/keystore2", + "aidl/android/hardware/security/keymint", + ]; + for dir in dirs { + println!("Processing AIDL files in directory: {}", dir); + let dir = fs::read_dir(dir).unwrap(); + + for entry in dir { + let entry = entry.unwrap(); + let path = entry.path(); + if path.extension().and_then(|s| s.to_str()) == Some("aidl") { + aidl = aidl.source(path); + } + } + } + + aidl.generate().unwrap(); + + let generated_path = PathBuf::from(format!("{}/aidl.rs", std::env::var("OUT_DIR").unwrap())); + let content = fs::read_to_string(&generated_path).unwrap(); + + let patched_content = content + .replace("SecurityLevel.", "super::super::super::hardware::security::keymint::SecurityLevel::SecurityLevel::") + .replace("HardwareAuthenticatorType.", "super::super::super::hardware::security::keymint::HardwareAuthenticatorType::HardwareAuthenticatorType::") + .replace("r#authenticatorType: super::Digest::Digest::NONE,", "r#authenticatorType: super::HardwareAuthenticatorType::HardwareAuthenticatorType::NONE,") + .replace("r#operation: rsbinder::Strong,", "r#operation: Option>,"); + + println!("Patched AIDL content:\n{}", generated_path.display()); + + fs::write(generated_path, patched_content).unwrap(); } \ No newline at end of file diff --git a/libs/rust/src/keymaster/database/mod.rs b/libs/rust/src/keymaster/database/mod.rs new file mode 100644 index 0000000..1a2c959 --- /dev/null +++ b/libs/rust/src/keymaster/database/mod.rs @@ -0,0 +1 @@ +pub mod perboot; \ No newline at end of file diff --git a/libs/rust/src/keymaster/database/perboot.rs b/libs/rust/src/keymaster/database/perboot.rs new file mode 100644 index 0000000..060c0ab --- /dev/null +++ b/libs/rust/src/keymaster/database/perboot.rs @@ -0,0 +1,107 @@ +// Copyright 2021, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module implements a per-boot, shared, in-memory storage of auth tokens +//! for the main Keystore 2.0 database module. + +use std::collections::HashSet; +use std::sync::Arc; +use std::sync::LazyLock; +use std::sync::RwLock; + +use crate::android::hardware::security::keymint::HardwareAuthToken::HardwareAuthToken; +use crate::android::hardware::security::keymint::HardwareAuthenticatorType::HardwareAuthenticatorType; +use crate::keymaster::db::AuthTokenEntry; + +#[derive(PartialEq, PartialOrd, Ord, Eq, Hash)] +struct AuthTokenId { + user_id: i64, + auth_id: i64, + authenticator_type: HardwareAuthenticatorType, +} + +impl AuthTokenId { + fn from_auth_token(tok: &HardwareAuthToken) -> Self { + AuthTokenId { + user_id: tok.userId, + auth_id: tok.authenticatorId, + authenticator_type: tok.authenticatorType, + } + } +} + +//Implements Eq/Hash to only operate on the AuthTokenId portion +//of the AuthTokenEntry. This allows a HashSet to DTRT. +#[derive(Clone)] +struct AuthTokenEntryWrap(AuthTokenEntry); + +impl std::hash::Hash for AuthTokenEntryWrap { + fn hash(&self, state: &mut H) { + AuthTokenId::from_auth_token(&self.0.auth_token).hash(state) + } +} + +impl PartialEq for AuthTokenEntryWrap { + fn eq(&self, other: &AuthTokenEntryWrap) -> bool { + AuthTokenId::from_auth_token(&self.0.auth_token) + == AuthTokenId::from_auth_token(&other.0.auth_token) + } +} + +impl Eq for AuthTokenEntryWrap {} + +/// Per-boot state structure. Currently only used to track auth tokens. +#[derive(Default)] +pub struct PerbootDB { + // We can use a .unwrap() discipline on this lock, because only panicking + // while holding a .write() lock will poison it. The only write usage is + // an insert call which inserts a pre-constructed pair. + auth_tokens: RwLock>, +} + +/// The global instance of the perboot DB. Located here rather than in globals +/// in order to restrict access to the database module. +pub static PERBOOT_DB: LazyLock> = LazyLock::new(|| Arc::new(PerbootDB::new())); + +impl PerbootDB { + /// Construct a new perboot database. Currently just uses default values. + pub fn new() -> Self { + Default::default() + } + /// Add a new auth token + timestamp to the database, replacing any which + /// match all of user_id, auth_id, and auth_type. + pub fn insert_auth_token_entry(&self, entry: AuthTokenEntry) { + self.auth_tokens.write().unwrap().replace(AuthTokenEntryWrap(entry)); + } + /// Locate an auth token entry which matches the predicate with the most + /// recent update time. + pub fn find_auth_token_entry bool>( + &self, + p: P, + ) -> Option { + let reader = self.auth_tokens.read().unwrap(); + let mut matches: Vec<_> = reader.iter().filter(|x| p(&x.0)).collect(); + matches.sort_by_key(|x| x.0.time_received); + matches.last().map(|x| x.0.clone()) + } + /// Return how many auth tokens are currently tracked. + pub fn auth_tokens_len(&self) -> usize { + self.auth_tokens.read().unwrap().len() + } + #[cfg(test)] + /// For testing, return all auth tokens currently tracked. + pub fn get_all_auth_token_entries(&self) -> Vec { + self.auth_tokens.read().unwrap().iter().cloned().map(|x| x.0).collect() + } +} diff --git a/libs/rust/src/keymaster/db.rs b/libs/rust/src/keymaster/db.rs new file mode 100644 index 0000000..5b376a5 --- /dev/null +++ b/libs/rust/src/keymaster/db.rs @@ -0,0 +1,298 @@ +use std::{path::Path, time::Duration}; + +use anyhow::{Context, Result}; +use rusqlite::{params, types::{FromSql, FromSqlResult, ToSqlOutput, Value, ValueRef}, Connection, ToSql, Transaction}; + +use crate::{android::hardware::security::keymint::{HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType}, utils::get_current_time_in_milliseconds}; + +const DB_ROOT_PATH: &str = "./omk/data"; + +#[cfg(target_os = "android")] +const DB_ROOT_PATH: &str = "/data/adb/omk/data"; + +const PERSISTENT_DB_FILENAME: &'static str = "keymaster.db"; + +const DB_BUSY_RETRY_INTERVAL: Duration = Duration::from_micros(500); + +pub struct KeymasterDb { + conn: Connection, +} + +impl KeymasterDb { + pub fn new() -> Result { + let path = format!("{}/{}", DB_ROOT_PATH, PERSISTENT_DB_FILENAME); + if let Some(p) = Path::new(&path).parent() { + std::fs::create_dir_all(p).context("Failed to create directory for database.")?; + } + + let mut conn = Self::make_connection(&path)?; + let init_table = conn + .transaction() + .context("Failed to create transaction for initializing database.")?; + Self::init_tables(&init_table)?; + init_table + .commit() + .context("Failed to commit transaction for initializing database.")?; + + Ok(KeymasterDb { conn }) + } + + pub fn connection(&self) -> &Connection { + &self.conn + } + + pub fn close(self) -> Result<()> { + self.conn + .close() + .map_err(|(_, e)| e) + .context("Failed to close database connection.") + } + + fn init_tables(tx: &Transaction) -> Result<()> { + tx.execute( + "CREATE TABLE IF NOT EXISTS persistent.keyentry ( + id INTEGER UNIQUE, + key_type INTEGER, + domain INTEGER, + namespace INTEGER, + alias BLOB, + state INTEGER, + km_uuid BLOB);", + [], + ) + .context("Failed to initialize \"keyentry\" table.")?; + + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.keyentry_id_index + ON keyentry(id);", + [], + ) + .context("Failed to create index keyentry_id_index.")?; + + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.keyentry_domain_namespace_index + ON keyentry(domain, namespace, alias);", + [], + ) + .context("Failed to create index keyentry_domain_namespace_index.")?; + + // Index added in v2 of database schema. + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.keyentry_state_index + ON keyentry(state);", + [], + ) + .context("Failed to create index keyentry_state_index.")?; + + tx.execute( + "CREATE TABLE IF NOT EXISTS persistent.blobentry ( + id INTEGER PRIMARY KEY, + subcomponent_type INTEGER, + keyentryid INTEGER, + blob BLOB, + state INTEGER DEFAULT 0);", // `state` added in v2 of schema + [], + ) + .context("Failed to initialize \"blobentry\" table.")?; + + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.blobentry_keyentryid_index + ON blobentry(keyentryid);", + [], + ) + .context("Failed to create index blobentry_keyentryid_index.")?; + + // Index added in v2 of database schema. + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.blobentry_state_index + ON blobentry(subcomponent_type, state);", + [], + ) + .context("Failed to create index blobentry_state_index.")?; + + tx.execute( + "CREATE TABLE IF NOT EXISTS persistent.blobmetadata ( + id INTEGER PRIMARY KEY, + blobentryid INTEGER, + tag INTEGER, + data ANY, + UNIQUE (blobentryid, tag));", + [], + ) + .context("Failed to initialize \"blobmetadata\" table.")?; + + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.blobmetadata_blobentryid_index + ON blobmetadata(blobentryid);", + [], + ) + .context("Failed to create index blobmetadata_blobentryid_index.")?; + + tx.execute( + "CREATE TABLE IF NOT EXISTS persistent.keyparameter ( + keyentryid INTEGER, + tag INTEGER, + data ANY, + security_level INTEGER);", + [], + ) + .context("Failed to initialize \"keyparameter\" table.")?; + + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.keyparameter_keyentryid_index + ON keyparameter(keyentryid);", + [], + ) + .context("Failed to create index keyparameter_keyentryid_index.")?; + + tx.execute( + "CREATE TABLE IF NOT EXISTS persistent.keymetadata ( + keyentryid INTEGER, + tag INTEGER, + data ANY, + UNIQUE (keyentryid, tag));", + [], + ) + .context("Failed to initialize \"keymetadata\" table.")?; + + tx.execute( + "CREATE INDEX IF NOT EXISTS persistent.keymetadata_keyentryid_index + ON keymetadata(keyentryid);", + [], + ) + .context("Failed to create index keymetadata_keyentryid_index.")?; + + tx.execute( + "CREATE TABLE IF NOT EXISTS persistent.grant ( + id INTEGER UNIQUE, + grantee INTEGER, + keyentryid INTEGER, + access_vector INTEGER);", + [], + ) + .context("Failed to initialize \"grant\" table.")?; + + Ok(()) + } + + fn make_connection(persistent_file: &str) -> Result { + let conn = + Connection::open_in_memory().context("Failed to initialize SQLite connection.")?; + + loop { + if let Err(e) = conn + .execute("ATTACH DATABASE ? as persistent;", params![persistent_file]) + .context("Failed to attach database persistent.") + { + if Self::is_locked_error(&e) { + std::thread::sleep(DB_BUSY_RETRY_INTERVAL); + continue; + } else { + return Err(e); + } + } + break; + } + + // Drop the cache size from default (2M) to 0.5M + conn.execute("PRAGMA persistent.cache_size = -500;", params![]) + .context("Failed to decrease cache size for persistent db")?; + + Ok(conn) + } + + fn is_locked_error(e: &anyhow::Error) -> bool { + matches!( + e.root_cause().downcast_ref::(), + Some(rusqlite::ffi::Error { + code: rusqlite::ErrorCode::DatabaseBusy, + .. + }) | Some(rusqlite::ffi::Error { + code: rusqlite::ErrorCode::DatabaseLocked, + .. + }) + ) + } +} + +/// Database representation of the monotonic time retrieved from the system call clock_gettime with +/// CLOCK_BOOTTIME. Stores monotonic time as i64 in milliseconds. +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct BootTime(i64); + +impl BootTime { + /// Constructs a new BootTime + pub fn now() -> Self { + Self(get_current_time_in_milliseconds()) + } + + /// Returns the value of BootTime in milliseconds as i64 + pub fn milliseconds(&self) -> i64 { + self.0 + } + + /// Returns the integer value of BootTime as i64 + pub fn seconds(&self) -> i64 { + self.0 / 1000 + } + + /// Like i64::checked_sub. + pub fn checked_sub(&self, other: &Self) -> Option { + self.0.checked_sub(other.0).map(Self) + } +} + +impl ToSql for BootTime { + fn to_sql(&self) -> rusqlite::Result { + Ok(ToSqlOutput::Owned(Value::Integer(self.0))) + } +} + +impl FromSql for BootTime { + fn column_result(value: ValueRef) -> FromSqlResult { + Ok(Self(i64::column_result(value)?)) + } +} + +/// This struct encapsulates the information to be stored in the database about the auth tokens +/// received by keystore. +#[derive(Clone)] +pub struct AuthTokenEntry { + pub(crate) auth_token: HardwareAuthToken, + // Time received in milliseconds + pub(crate) time_received: BootTime, +} + +impl AuthTokenEntry { + fn new(auth_token: HardwareAuthToken, time_received: BootTime) -> Self { + AuthTokenEntry { auth_token, time_received } + } + + /// Checks if this auth token satisfies the given authentication information. + pub fn satisfies(&self, user_secure_ids: &[i64], auth_type: HardwareAuthenticatorType) -> bool { + user_secure_ids.iter().any(|&sid| { + (sid == self.auth_token.userId || sid == self.auth_token.authenticatorId) + && ((auth_type.0 & self.auth_token.authenticatorType.0) != 0) + }) + } + + /// Returns the auth token wrapped by the AuthTokenEntry + pub fn auth_token(&self) -> &HardwareAuthToken { + &self.auth_token + } + + /// Returns the auth token wrapped by the AuthTokenEntry + pub fn take_auth_token(self) -> HardwareAuthToken { + self.auth_token + } + + /// Returns the time that this auth token was received. + pub fn time_received(&self) -> BootTime { + self.time_received + } + + /// Returns the challenge value of the auth token. + pub fn challenge(&self) -> i64 { + self.auth_token.challenge + } +} diff --git a/libs/rust/src/keymaster/mod.rs b/libs/rust/src/keymaster/mod.rs new file mode 100644 index 0000000..b8b00b1 --- /dev/null +++ b/libs/rust/src/keymaster/mod.rs @@ -0,0 +1,2 @@ +pub mod db; +pub mod database; \ No newline at end of file diff --git a/libs/rust/src/attest.rs b/libs/rust/src/keymint/attest.rs similarity index 100% rename from libs/rust/src/attest.rs rename to libs/rust/src/keymint/attest.rs diff --git a/libs/rust/src/clock.rs b/libs/rust/src/keymint/clock.rs similarity index 100% rename from libs/rust/src/clock.rs rename to libs/rust/src/keymint/clock.rs diff --git a/libs/rust/src/keymint/mod.rs b/libs/rust/src/keymint/mod.rs new file mode 100644 index 0000000..fe93f4f --- /dev/null +++ b/libs/rust/src/keymint/mod.rs @@ -0,0 +1,5 @@ +pub mod attest; +pub mod clock; +pub mod rpc; +pub mod sdd; +pub mod soft; \ No newline at end of file diff --git a/libs/rust/src/rpc.rs b/libs/rust/src/keymint/rpc.rs similarity index 100% rename from libs/rust/src/rpc.rs rename to libs/rust/src/keymint/rpc.rs diff --git a/libs/rust/src/sdd.rs b/libs/rust/src/keymint/sdd.rs similarity index 100% rename from libs/rust/src/sdd.rs rename to libs/rust/src/keymint/sdd.rs diff --git a/libs/rust/src/soft.rs b/libs/rust/src/keymint/soft.rs similarity index 95% rename from libs/rust/src/soft.rs rename to libs/rust/src/keymint/soft.rs index 73a08cf..60ee825 100644 --- a/libs/rust/src/soft.rs +++ b/libs/rust/src/keymint/soft.rs @@ -57,11 +57,11 @@ impl Default for Derive { } } -impl crate::rpc::DeriveBytes for Derive { +impl crate::keymint::rpc::DeriveBytes for Derive { fn derive_bytes(&self, context: &[u8], output_len: usize) -> Result, Error> { BoringHmac.hkdf(&[], &self.hbk, context, output_len) } } /// RPC artifact retrieval using software fake key. -pub type RpcArtifacts = crate::rpc::Artifacts; +pub type RpcArtifacts = crate::keymint::rpc::Artifacts; diff --git a/libs/rust/src/main.rs b/libs/rust/src/main.rs index 5d03806..1f604ba 100644 --- a/libs/rust/src/main.rs +++ b/libs/rust/src/main.rs @@ -1,22 +1,22 @@ -use std::fmt::Debug; - -use kmr_common::crypto::{self, des::Key, MonotonicClock}; +use kmr_common::crypto::{self, MonotonicClock}; use kmr_crypto_boring::{ aes::BoringAes, aes_cmac::BoringAesCmac, des::BoringDes, ec::BoringEc, eq::BoringEq, hmac::BoringHmac, rng::BoringRng, rsa::BoringRsa, sha256::BoringSha256 }; use kmr_ta::{device::{CsrSigningAlgorithm, Implementation}, HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; -use kmr_wire::{cbor::de, keymint::{AttestationKey, DateTime, KeyCharacteristics, KeyParam, KeyPurpose, SecurityLevel}, rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR, sharedsecret::SharedSecretParameters, GenerateKeyRequest, GetHardwareInfoRequest, GetRootOfTrustRequest, GetSharedSecretParametersRequest, KeySizeInBits, PerformOpReq}; +use kmr_wire::{keymint::{DateTime, KeyCharacteristics, KeyParam, KeyPurpose, SecurityLevel}, rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR, GenerateKeyRequest, GetHardwareInfoRequest, KeySizeInBits, PerformOpReq}; use log::{debug, error}; -use kmr_wire::AsCborValue; + +use crate::keymint::{attest, clock, sdd, soft}; pub mod macros; -pub mod attest; -pub mod sdd; -pub mod clock; pub mod proto; -pub mod soft; -pub mod rpc; +pub mod keymint; +pub mod keymaster; pub mod logging; +pub mod utils; + +include!(concat!(env!("OUT_DIR"), "/aidl.rs")); +// include!( "./aidl.rs"); // for development only #[cfg(target_os = "android")] const TAG: &str = "OhMyKeymint"; @@ -24,8 +24,11 @@ const TAG: &str = "OhMyKeymint"; fn main(){ debug!("Hello, OhMyKeymint!"); logging::init_logger(); + let db = keymaster::db::KeymasterDb::new().unwrap(); + db.close().unwrap(); + #[cfg(target_os = "android")] logi!(TAG, "Application started"); - let security_level = SecurityLevel::TrustedEnvironment; + let security_level: SecurityLevel = SecurityLevel::TrustedEnvironment; let hw_info = HardwareInfo { version_number: 2, security_level, @@ -146,31 +149,6 @@ fn main(){ let clock = clock::StdClock; let current_time = clock.now().0; - let keyblob = kmr_common::keyblob::EncryptedKeyBlobV1 { - characteristics: vec![KeyCharacteristics { - security_level: SecurityLevel::TrustedEnvironment, - authorizations: vec![ - KeyParam::ApplicationId("com.example.app".as_bytes().to_vec()), - KeyParam::AttestationApplicationId("com.example.app".as_bytes().to_vec()), - KeyParam::Purpose(KeyPurpose::AttestKey), - KeyParam::KeySize(KeySizeInBits(256)), - KeyParam::Algorithm(kmr_wire::keymint::Algorithm::Ec), - KeyParam::EcCurve(kmr_wire::keymint::EcCurve::P256), - KeyParam::Digest(kmr_wire::keymint::Digest::Sha256), - KeyParam::NoAuthRequired, - KeyParam::CertificateNotBefore(DateTime{ms_since_epoch: clock.now().0 - 10000}), // -10 seconds - KeyParam::CertificateNotAfter(DateTime{ms_since_epoch: clock.now().0 + 31536000000}), // +1 year - KeyParam::CertificateSerial(b"1234567890".to_vec()), - KeyParam::CertificateSubject(b"CN=Android Keystore Key".to_vec()), - KeyParam::AttestationChallenge(b"Test Attestation Challenge".to_vec()), - - ], - }], - key_derivation_input: [0u8; 32], - kek_context: vec![0; 32], - encrypted_key_material: kmr_wire::coset::CoseEncrypt0::default(), - secure_deletion_slot: None, - }; let req = PerformOpReq::DeviceGenerateKey(GenerateKeyRequest{ key_params: vec![ diff --git a/libs/rust/src/utils.rs b/libs/rust/src/utils.rs new file mode 100644 index 0000000..05e6a12 --- /dev/null +++ b/libs/rust/src/utils.rs @@ -0,0 +1,10 @@ +/// This returns the current time (in milliseconds) as an instance of a monotonic clock, +/// by invoking the system call since Rust does not support getting monotonic time instance +/// as an integer. +pub fn get_current_time_in_milliseconds() -> i64 { + let mut current_time = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + // SAFETY: The pointer is valid because it comes from a reference, and clock_gettime doesn't + // retain it beyond the call. + unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut current_time) }; + current_time.tv_sec as i64 * 1000 + (current_time.tv_nsec as i64 / 1_000_000) +} \ No newline at end of file From a5ea1013ee51c7ac5697be4bc26a8f296f429563 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 4 Oct 2025 02:34:08 +0800 Subject: [PATCH 04/46] fix proto mod Signed-off-by: qwq233 --- libs/rust/src/.gitignore | 3 ++- libs/rust/src/proto/mod.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 libs/rust/src/proto/mod.rs diff --git a/libs/rust/src/.gitignore b/libs/rust/src/.gitignore index 1337e90..a9f2cc2 100644 --- a/libs/rust/src/.gitignore +++ b/libs/rust/src/.gitignore @@ -1 +1,2 @@ -/proto \ No newline at end of file +/proto +!/proto/mod.rs diff --git a/libs/rust/src/proto/mod.rs b/libs/rust/src/proto/mod.rs new file mode 100644 index 0000000..e626ba5 --- /dev/null +++ b/libs/rust/src/proto/mod.rs @@ -0,0 +1 @@ +pub mod storage; \ No newline at end of file From 57a30750c5ddecf8d67e5852bbf00ca1d02c149b Mon Sep 17 00:00:00 2001 From: qwq233 Date: Tue, 7 Oct 2025 21:18:11 +0800 Subject: [PATCH 05/46] saving progress Signed-off-by: qwq233 --- libs/rust/.gitignore | 4 + libs/rust/Cargo.toml | 20 +- .../android/content/pm/IPackageManager.aidl | 22 + .../security/authorization/ResponseCode.aidl | 54 + libs/rust/boringssl/Cargo.toml | 7 +- libs/rust/boringssl/src/aes.rs | 42 +- libs/rust/boringssl/src/aes_cmac.rs | 23 +- libs/rust/boringssl/src/des.rs | 9 +- libs/rust/boringssl/src/ec.rs | 110 +- libs/rust/boringssl/src/error.rs | 101 + libs/rust/boringssl/src/hmac.rs | 19 +- libs/rust/boringssl/src/km.rs | 526 +++++ libs/rust/boringssl/src/lib.rs | 3 + libs/rust/boringssl/src/rng.rs | 5 +- libs/rust/boringssl/src/rsa.rs | 99 +- libs/rust/boringssl/src/zvec.rs | 150 ++ libs/rust/build.rs | 36 +- libs/rust/common/src/bin/keyblob-cddl-dump.rs | 21 +- libs/rust/common/src/crypto.rs | 27 +- libs/rust/common/src/crypto/aes.rs | 21 +- libs/rust/common/src/crypto/des.rs | 12 +- libs/rust/common/src/crypto/ec.rs | 98 +- libs/rust/common/src/crypto/hmac.rs | 12 +- libs/rust/common/src/crypto/rsa.rs | 16 +- libs/rust/common/src/crypto/traits.rs | 6 +- libs/rust/common/src/keyblob.rs | 45 +- libs/rust/common/src/keyblob/legacy.rs | 43 +- libs/rust/common/src/keyblob/tests.rs | 24 +- libs/rust/common/src/tag.rs | 179 +- libs/rust/common/src/tag/info.rs | 9 +- libs/rust/common/src/tag/info/tests.rs | 20 +- libs/rust/common/src/tag/legacy.rs | 41 +- libs/rust/common/src/tag/tests.rs | 34 +- libs/rust/derive/tests/integration_test.rs | 15 +- libs/rust/device.prop | 155 ++ libs/rust/rsproperties-service/Cargo.toml | 21 + libs/rust/rsproperties-service/README | 1 + libs/rust/rsproperties-service/README.md | 309 +++ .../examples/example_service.rs | 80 + libs/rust/rsproperties-service/src/lib.rs | 86 + .../src/properties_service.rs | 166 ++ .../src/socket_service.rs | 378 ++++ .../rust/rsproperties-service/tests/common.rs | 74 + .../tests/comprehensive_api_tests.rs | 482 +++++ .../tests/error_handling_tests.rs | 507 +++++ .../tests/example_usage_tests.rs | 305 +++ .../tests/final_comprehensive_tests.rs | 493 +++++ .../tests/integration_tests.rs | 291 +++ .../tests/new_api_showcase.rs | 407 ++++ .../tests/parsed_api_tests.rs | 319 +++ .../tests/performance_tests.rs | 381 ++++ .../tests/set_api_tests.rs | 536 +++++ libs/rust/src/consts.rs | 9 + libs/rust/src/global.rs | 92 + .../src/keymaster/attestation_key_utils.rs | 117 ++ libs/rust/src/keymaster/boot_key.rs | 304 +++ libs/rust/src/keymaster/crypto.rs | 100 + libs/rust/src/keymaster/database/mod.rs | 3 +- libs/rust/src/keymaster/database/perboot.rs | 13 +- libs/rust/src/keymaster/database/utils.rs | 233 +++ libs/rust/src/keymaster/db.rs | 1805 ++++++++++++++++- libs/rust/src/keymaster/enforcements.rs | 832 ++++++++ libs/rust/src/keymaster/error.rs | 54 + libs/rust/src/keymaster/key_parameter.rs | 1100 ++++++++++ libs/rust/src/keymaster/keymint_device.rs | 506 +++++ libs/rust/src/keymaster/mod.rs | 13 +- libs/rust/src/keymaster/permission.rs | 437 ++++ libs/rust/src/keymaster/security_level.rs | 456 +++++ libs/rust/src/keymaster/super_key.rs | 1270 ++++++++++++ libs/rust/src/keymaster/utils.rs | 136 ++ libs/rust/src/keymint/clock.rs | 5 +- libs/rust/src/keymint/mod.rs | 2 +- libs/rust/src/keymint/sdd.rs | 10 +- libs/rust/src/lib.rs | 62 + libs/rust/src/logging.rs | 12 + libs/rust/src/macros.rs | 50 +- libs/rust/src/main.rs | 178 +- libs/rust/src/plat/mod.rs | 2 + libs/rust/src/plat/property_watcher.rs | 89 + libs/rust/src/plat/utils.rs | 71 + libs/rust/src/proto/mod.rs | 2 +- libs/rust/src/utils.rs | 21 +- libs/rust/src/watchdog.rs | 48 + libs/rust/ta/src/cert.rs | 172 +- libs/rust/ta/src/clock.rs | 9 +- libs/rust/ta/src/device.rs | 3 +- libs/rust/ta/src/keys.rs | 161 +- libs/rust/ta/src/lib.rs | 190 +- libs/rust/ta/src/operation.rs | 97 +- libs/rust/ta/src/rkp.rs | 68 +- libs/rust/ta/src/secret.rs | 28 +- libs/rust/ta/src/tests.rs | 79 +- libs/rust/tests/src/bin/auth-keyblob-parse.rs | 15 +- .../tests/src/bin/encrypted-keyblob-parse.rs | 5 +- libs/rust/tests/src/lib.rs | 89 +- libs/rust/tests/tests/keyblob_test.rs | 6 +- libs/rust/watchdog/Cargo.toml | 19 + libs/rust/watchdog/src/lib.rs | 395 ++++ libs/rust/wire/src/keymint.rs | 37 +- libs/rust/wire/src/legacy.rs | 43 +- libs/rust/wire/src/lib.rs | 24 +- libs/rust/wire/src/tests.rs | 12 +- libs/rust/wire/src/types.rs | 2 + 103 files changed, 15723 insertions(+), 607 deletions(-) create mode 100644 libs/rust/aidl/android/content/pm/IPackageManager.aidl create mode 100644 libs/rust/aidl/android/security/authorization/ResponseCode.aidl create mode 100644 libs/rust/boringssl/src/error.rs create mode 100644 libs/rust/boringssl/src/km.rs create mode 100644 libs/rust/boringssl/src/zvec.rs create mode 100644 libs/rust/device.prop create mode 100644 libs/rust/rsproperties-service/Cargo.toml create mode 100644 libs/rust/rsproperties-service/README create mode 100644 libs/rust/rsproperties-service/README.md create mode 100644 libs/rust/rsproperties-service/examples/example_service.rs create mode 100644 libs/rust/rsproperties-service/src/lib.rs create mode 100644 libs/rust/rsproperties-service/src/properties_service.rs create mode 100644 libs/rust/rsproperties-service/src/socket_service.rs create mode 100644 libs/rust/rsproperties-service/tests/common.rs create mode 100644 libs/rust/rsproperties-service/tests/comprehensive_api_tests.rs create mode 100644 libs/rust/rsproperties-service/tests/error_handling_tests.rs create mode 100644 libs/rust/rsproperties-service/tests/example_usage_tests.rs create mode 100644 libs/rust/rsproperties-service/tests/final_comprehensive_tests.rs create mode 100644 libs/rust/rsproperties-service/tests/integration_tests.rs create mode 100644 libs/rust/rsproperties-service/tests/new_api_showcase.rs create mode 100644 libs/rust/rsproperties-service/tests/parsed_api_tests.rs create mode 100644 libs/rust/rsproperties-service/tests/performance_tests.rs create mode 100644 libs/rust/rsproperties-service/tests/set_api_tests.rs create mode 100644 libs/rust/src/consts.rs create mode 100644 libs/rust/src/global.rs create mode 100644 libs/rust/src/keymaster/attestation_key_utils.rs create mode 100644 libs/rust/src/keymaster/boot_key.rs create mode 100644 libs/rust/src/keymaster/crypto.rs create mode 100644 libs/rust/src/keymaster/database/utils.rs create mode 100644 libs/rust/src/keymaster/enforcements.rs create mode 100644 libs/rust/src/keymaster/error.rs create mode 100644 libs/rust/src/keymaster/key_parameter.rs create mode 100644 libs/rust/src/keymaster/keymint_device.rs create mode 100644 libs/rust/src/keymaster/permission.rs create mode 100644 libs/rust/src/keymaster/security_level.rs create mode 100644 libs/rust/src/keymaster/super_key.rs create mode 100644 libs/rust/src/keymaster/utils.rs create mode 100644 libs/rust/src/plat/mod.rs create mode 100644 libs/rust/src/plat/property_watcher.rs create mode 100644 libs/rust/src/plat/utils.rs create mode 100644 libs/rust/src/watchdog.rs create mode 100644 libs/rust/watchdog/Cargo.toml create mode 100644 libs/rust/watchdog/src/lib.rs diff --git a/libs/rust/.gitignore b/libs/rust/.gitignore index 7da0ed1..fcc1585 100644 --- a/libs/rust/.gitignore +++ b/libs/rust/.gitignore @@ -19,4 +19,8 @@ Cargo.lock /omk +# development files +hex.txt +parcel.bin + src/aidl.rs \ No newline at end of file diff --git a/libs/rust/Cargo.toml b/libs/rust/Cargo.toml index 9578185..047dc85 100644 --- a/libs/rust/Cargo.toml +++ b/libs/rust/Cargo.toml @@ -35,9 +35,21 @@ rusqlite = { version = "0.37.0", features = ["bundled"] } anyhow = "1.0.100" async-trait = "0.1.89" tokio = { version = "1.47.1", features = ["macros", "libc"] } +lazy_static = "1.5.0" +thiserror = "2.0.17" +serde = "1.0.228" +x509-cert = "0.2.5" +watchdog_rs = "*" +nix = { version = "0.30.1", features = ["mman"] } +rsproperties = { version = "0.2.2", features = ["builder"] } +libsqlite3-sys = "0.35.0" +rand = "0.9.2" + +[target.'cfg(target_os = "linux")'.dependencies] +rsproperties-service = "*" [target.'cfg(target_os = "android")'.dependencies] -android_logger_lite = "0.1.0" +android_logger = "0.15.1" [profile.release] @@ -57,6 +69,8 @@ members = [ "ta", "tests", "wire", + "watchdog", + "rsproperties-service", ] [patch.crates-io] @@ -66,7 +80,9 @@ kmr-crypto-boring = { path = "boringssl" } kmr-ta = { path = "ta" } kmr-tests = { path = "tests" } kmr-wire = { path = "wire" } +watchdog_rs = { path = "watchdog" } +rsproperties-service = { path = "rsproperties-service" } [build-dependencies] prost-build = "0.14.1" -rsbinder-aidl = "0.4.2" +rsbinder-aidl = "0.4.3" diff --git a/libs/rust/aidl/android/content/pm/IPackageManager.aidl b/libs/rust/aidl/android/content/pm/IPackageManager.aidl new file mode 100644 index 0000000..9465917 --- /dev/null +++ b/libs/rust/aidl/android/content/pm/IPackageManager.aidl @@ -0,0 +1,22 @@ + +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +package android.content.pm; + +interface IPackageManager { + String[] getPackagesForUid(int uid); +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/authorization/ResponseCode.aidl b/libs/rust/aidl/android/security/authorization/ResponseCode.aidl new file mode 100644 index 0000000..6e2ca28 --- /dev/null +++ b/libs/rust/aidl/android/security/authorization/ResponseCode.aidl @@ -0,0 +1,54 @@ +// Copyright 2021, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package android.security.authorization; +/** + * Used as exception codes by IKeystoreAuthorization. + * @hide + */ +@Backing(type="int") +enum ResponseCode { + /** + * A matching auth token is not found. + */ + NO_AUTH_TOKEN_FOUND = 1, + /** + * The matching auth token is expired. + */ + AUTH_TOKEN_EXPIRED = 2, + /** + * Same as in keystore2/ResponseCode.aidl. + * Any unexpected Error such as IO or communication errors. + */ + SYSTEM_ERROR = 4, + /** + * Same as in keystore2/ResponseCode.aidl. + * Indicates that the caller does not have the permissions for the attempted request. + */ + PERMISSION_DENIED = 6, + /** + * Same as in keystore2/ResponseCode.aidl. + * Indicates that the requested key does not exist. + */ + KEY_NOT_FOUND = 7, + /** + * Same as in keystore2/ResponseCode.aidl. + * Indicates that a value being processed is corrupted. + */ + VALUE_CORRUPTED = 8, + /** + * Same as in keystore2/ResponseCode.aidl. + * Indicates that an invalid argument was passed to an API call. + */ + INVALID_ARGUMENT = 20, + } diff --git a/libs/rust/boringssl/Cargo.toml b/libs/rust/boringssl/Cargo.toml index 2598dfd..5438c06 100644 --- a/libs/rust/boringssl/Cargo.toml +++ b/libs/rust/boringssl/Cargo.toml @@ -9,16 +9,19 @@ edition = "2021" license = "Apache-2.0" [dependencies] -ffi = { package = "openssl-sys", version = "^0.9.75" } +ffi = { package = "openssl-sys", version = "^0.9.75", features = ["bindgen"] } foreign-types = "0.3.1" kmr-common = "*" kmr-wire = "*" libc = "^0.2.112" log = "^0.4" openssl = "^0.10.36" +anyhow = "*" +thiserror = "*" +nix = "*" [dev-dependencies] kmr-tests = "*" [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(soong)'] } \ No newline at end of file +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(soong)'] } diff --git a/libs/rust/boringssl/src/aes.rs b/libs/rust/boringssl/src/aes.rs index f591eb6..abd5d2a 100644 --- a/libs/rust/boringssl/src/aes.rs +++ b/libs/rust/boringssl/src/aes.rs @@ -14,14 +14,14 @@ //! BoringSSL-based implementation of AES. use crate::{openssl_err, openssl_err_or, ossl}; -use Box; -use Vec; use core::cmp::min; use kmr_common::{ crypto, crypto::OpaqueOr, explicit, km_err, vec_try, vec_try_with_capacity, Error, FallibleAllocExt, }; use openssl::symm::{Cipher, Crypter}; +use Box; +use Vec; /// [`crypto::Aes`] implementation based on BoringSSL. pub struct BoringAes; @@ -118,7 +118,10 @@ impl crypto::Aes for BoringAes { Ok(match dir { crypto::SymmetricOperation::Encrypt => Box::new({ - BoringAesGcmEncryptOperation { mode, inner: BoringAesOperation { crypter } } + BoringAesGcmEncryptOperation { + mode, + inner: BoringAesOperation { crypter }, + } }), crypto::SymmetricOperation::Decrypt => Box::new(BoringAesGcmDecryptOperation { crypter, @@ -140,7 +143,10 @@ impl crypto::EmittingOperation for BoringAesOperation { let out_len = self .crypter .update(data, &mut output) - .map_err(openssl_err!("update {} bytes from input failed", data.len()))?; + .map_err(openssl_err!( + "update {} bytes from input failed", + data.len() + ))?; output.truncate(out_len); Ok(output) } @@ -181,7 +187,10 @@ impl crypto::EmittingOperation for BoringAesGcmEncryptOperation { self.inner .crypter .get_tag(&mut output[offset..offset + self.mode.tag_len()]) - .map_err(openssl_err!("failed to get tag of len {}", self.mode.tag_len()))?; + .map_err(openssl_err!( + "failed to get tag of len {}", + self.mode.tag_len() + ))?; output.truncate(offset + self.mode.tag_len()); Ok(output) } @@ -229,7 +238,10 @@ impl crypto::EmittingOperation for BoringAesGcmDecryptOperation { if cipherable_from_pending > 0 { offset = self .crypter - .update(&self.pending_input_tail[..cipherable_from_pending], &mut output) + .update( + &self.pending_input_tail[..cipherable_from_pending], + &mut output, + ) .map_err(openssl_err!( "update {} bytes from pending failed", cipherable_from_pending @@ -239,7 +251,10 @@ impl crypto::EmittingOperation for BoringAesGcmDecryptOperation { let out_len = self .crypter .update(&data[..cipherable_from_data], &mut output[offset..]) - .map_err(openssl_err!("update {} bytes from input failed", cipherable_from_data))?; + .map_err(openssl_err!( + "update {} bytes from input failed", + cipherable_from_data + ))?; offset += out_len; } output.truncate(offset); @@ -247,7 +262,8 @@ impl crypto::EmittingOperation for BoringAesGcmDecryptOperation { // Reset `self.pending_input_tail` to the unused data. let leftover_pending = self.pending_input_tail.len() - cipherable_from_pending; self.pending_input_tail.resize(self.decrypt_tag_len, 0); - self.pending_input_tail.copy_within(cipherable_from_pending.., 0); + self.pending_input_tail + .copy_within(cipherable_from_pending.., 0); self.pending_input_tail[leftover_pending..].copy_from_slice(&data[cipherable_from_data..]); Ok(output) @@ -263,10 +279,12 @@ impl crypto::EmittingOperation for BoringAesGcmDecryptOperation { self.decrypt_tag_len )); } - self.crypter.set_tag(&self.pending_input_tail).map_err(openssl_err!( - "failed to set {} bytes of tag", - self.pending_input_tail.len() - ))?; + self.crypter + .set_tag(&self.pending_input_tail) + .map_err(openssl_err!( + "failed to set {} bytes of tag", + self.pending_input_tail.len() + ))?; // Feeding in just the tag should not result in any output data. let mut output = Vec::new(); diff --git a/libs/rust/boringssl/src/aes_cmac.rs b/libs/rust/boringssl/src/aes_cmac.rs index 37ca00c..c0263ef 100644 --- a/libs/rust/boringssl/src/aes_cmac.rs +++ b/libs/rust/boringssl/src/aes_cmac.rs @@ -15,12 +15,13 @@ //! BoringSSL-based implementation of AES-CMAC. use crate::types::CmacCtx; use crate::{malloc_err, openssl_last_err}; -use Box; -use Vec; -#[cfg(target_os = "android")] use bssl_sys as ffi; +#[cfg(target_os = "android")] +use bssl_sys as ffi; use ffi; use kmr_common::{crypto, crypto::OpaqueOr, explicit, km_err, vec_try, Error}; use log::error; +use Box; +use Vec; /// [`crypto::AesCmac`] implementation based on BoringSSL. pub struct BoringAesCmac; @@ -91,7 +92,9 @@ impl core::ops::Drop for BoringAesCmacOperation { impl crypto::AccumulatingOperation for BoringAesCmacOperation { fn update(&mut self, data: &[u8]) -> Result<(), Error> { // Safety: `self.ctx` is non-null and valid, and `data` is a valid slice. - let result = unsafe { ffi::CMAC_Update(self.ctx.0, data.as_ptr() as *const libc::c_void, data.len()) }; + let result = unsafe { + ffi::CMAC_Update(self.ctx.0, data.as_ptr() as *const libc::c_void, data.len()) + }; if result != 1 { return Err(openssl_last_err()); } @@ -104,13 +107,21 @@ impl crypto::AccumulatingOperation for BoringAesCmacOperation { // Safety: `self.ctx` is non-null and valid; `output_len` is correct size of `output` // buffer. let result = unsafe { - ffi::CMAC_Final(self.ctx.0, output.as_mut_ptr(), &mut output_len as *mut usize) + ffi::CMAC_Final( + self.ctx.0, + output.as_mut_ptr(), + &mut output_len as *mut usize, + ) }; if result != 1 { return Err(openssl_last_err()); } if output_len != crypto::aes::BLOCK_SIZE { - return Err(km_err!(BoringSslError, "Unexpected CMAC output size of {}", output_len)); + return Err(km_err!( + BoringSslError, + "Unexpected CMAC output size of {}", + output_len + )); } Ok(output) } diff --git a/libs/rust/boringssl/src/des.rs b/libs/rust/boringssl/src/des.rs index 1b0f6de..7c349a4 100644 --- a/libs/rust/boringssl/src/des.rs +++ b/libs/rust/boringssl/src/des.rs @@ -14,10 +14,10 @@ //! BoringSSL-based implementation of 3-DES. use crate::{openssl_err, ossl}; -use Box; -use Vec; use kmr_common::{crypto, crypto::OpaqueOr, explicit, vec_try, Error}; use openssl::symm::{Cipher, Crypter}; +use Box; +use Vec; /// [`crypto::Des`] implementation based on BoringSSL. pub struct BoringDes; @@ -76,7 +76,10 @@ impl crypto::EmittingOperation for BoringDesOperation { let out_len = self .crypter .update(data, &mut output[..]) - .map_err(openssl_err!("update with {} bytes of data failed", data.len()))?; + .map_err(openssl_err!( + "update with {} bytes of data failed", + data.len() + ))?; output.truncate(out_len); Ok(output) } diff --git a/libs/rust/boringssl/src/ec.rs b/libs/rust/boringssl/src/ec.rs index f19c534..00a3054 100644 --- a/libs/rust/boringssl/src/ec.rs +++ b/libs/rust/boringssl/src/ec.rs @@ -15,8 +15,6 @@ //! BoringSSL-based implementation of elliptic curve functionality. use crate::types::{EvpMdCtx, EvpPkeyCtx}; use crate::{cvt, cvt_p, digest_into_openssl, openssl_err, openssl_last_err, ossl}; -use Box; -use Vec; #[cfg(soong)] use bssl_sys as ffi; use core::ops::DerefMut; @@ -32,6 +30,8 @@ use kmr_wire::{ keymint::{Digest, EcCurve}, }; use openssl::hash::MessageDigest; +use Box; +use Vec; #[cfg(soong)] fn private_key_from_der_for_group( @@ -60,7 +60,9 @@ pub struct BoringEc { impl core::default::Default for BoringEc { fn default() -> Self { ffi::init(); - Self { _priv: core::marker::PhantomData } + Self { + _priv: core::marker::PhantomData, + } } } @@ -81,7 +83,11 @@ impl crypto::Ec for BoringEc { ec::NistCurve::P384 => Key::P384(nist_key), ec::NistCurve::P521 => Key::P521(nist_key), }; - Ok(crypto::KeyMaterial::Ec(curve.into(), CurveType::Nist, key.into())) + Ok(crypto::KeyMaterial::Ec( + curve.into(), + CurveType::Nist, + key.into(), + )) } fn generate_ed25519_key( @@ -92,10 +98,18 @@ impl crypto::Ec for BoringEc { let pkey = ossl!(openssl::pkey::PKey::generate_ed25519())?; let key = ossl!(pkey.raw_private_key())?; let key: [u8; ec::CURVE25519_PRIV_KEY_LEN] = key.try_into().map_err(|e| { - km_err!(UnsupportedKeySize, "generated Ed25519 key of unexpected size: {:?}", e) + km_err!( + UnsupportedKeySize, + "generated Ed25519 key of unexpected size: {:?}", + e + ) })?; let key = Key::Ed25519(ec::Ed25519Key(key)); - Ok(crypto::KeyMaterial::Ec(EcCurve::Curve25519, CurveType::EdDsa, key.into())) + Ok(crypto::KeyMaterial::Ec( + EcCurve::Curve25519, + CurveType::EdDsa, + key.into(), + )) } fn generate_x25519_key( @@ -106,10 +120,18 @@ impl crypto::Ec for BoringEc { let pkey = ossl!(openssl::pkey::PKey::generate_x25519())?; let key = ossl!(pkey.raw_private_key())?; let key: [u8; ec::CURVE25519_PRIV_KEY_LEN] = key.try_into().map_err(|e| { - km_err!(UnsupportedKeySize, "generated X25519 key of unexpected size: {:?}", e) + km_err!( + UnsupportedKeySize, + "generated X25519 key of unexpected size: {:?}", + e + ) })?; let key = Key::X25519(ec::X25519Key(key)); - Ok(crypto::KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, key.into())) + Ok(crypto::KeyMaterial::Ec( + EcCurve::Curve25519, + CurveType::Xdh, + key.into(), + )) } fn nist_public_key(&self, key: &ec::NistKey, curve: ec::NistCurve) -> Result, Error> { @@ -158,7 +180,11 @@ impl crypto::Ec for BoringEc { // // Round up a bit just in case. let max_size = 164; - Ok(Box::new(BoringEcAgreeOperation { key, pending_input: Vec::new(), max_size })) + Ok(Box::new(BoringEcAgreeOperation { + key, + pending_input: Vec::new(), + max_size, + })) } fn begin_sign( @@ -172,15 +198,18 @@ impl crypto::Ec for BoringEc { Key::P224(key) | Key::P256(key) | Key::P384(key) | Key::P521(key) => { let curve = ec::NistCurve::try_from(curve)?; if let Some(digest) = digest_into_openssl(digest) { - Ok(Box::new(BoringEcDigestSignOperation::new(key, curve, digest)?)) + Ok(Box::new(BoringEcDigestSignOperation::new( + key, curve, digest, + )?)) } else { Ok(Box::new(BoringEcUndigestSignOperation::new(key, curve)?)) } } Key::Ed25519(key) => Ok(Box::new(BoringEd25519SignOperation::new(key)?)), - Key::X25519(_) => { - Err(km_err!(IncompatibleAlgorithm, "X25519 key not valid for signing")) - } + Key::X25519(_) => Err(km_err!( + IncompatibleAlgorithm, + "X25519 key not valid for signing" + )), } } } @@ -204,7 +233,9 @@ impl crypto::AccumulatingOperation for BoringEcAgreeOperation { } fn finish(self: Box) -> Result, Error> { - let peer_key = ossl!(openssl::pkey::PKey::public_key_from_der(&self.pending_input))?; + let peer_key = ossl!(openssl::pkey::PKey::public_key_from_der( + &self.pending_input + ))?; match &self.key { Key::P224(key) | Key::P256(key) | Key::P384(key) | Key::P521(key) => { let group = nist_key_to_group(&self.key)?; @@ -221,8 +252,9 @@ impl crypto::AccumulatingOperation for BoringEcAgreeOperation { // The BoringSSL `EVP_PKEY` interface does not support X25519, so need to invoke the // `ffi:X25519()` method directly. First need to extract the raw peer key from the // `SubjectPublicKeyInfo` it arrives in. - let peer_key = - ossl!(openssl::pkey::PKey::public_key_from_der(&self.pending_input))?; + let peer_key = ossl!(openssl::pkey::PKey::public_key_from_der( + &self.pending_input + ))?; let peer_key_type = peer_key.id(); if peer_key_type != openssl::pkey::Id::X25519 { return Err(km_err!( @@ -244,7 +276,11 @@ impl crypto::AccumulatingOperation for BoringEcAgreeOperation { // Safety: all pointer arguments need to point to 32-byte memory areas, enforced // above and in the definition of [`ec::X25519Key`]. let result = unsafe { - ffi::X25519(sig.as_mut_ptr(), &key.0 as *const u8, peer_key_data.as_ptr()) + ffi::X25519( + sig.as_mut_ptr(), + &key.0 as *const u8, + peer_key_data.as_ptr(), + ) }; if result == 1 { Ok(sig) @@ -254,9 +290,10 @@ impl crypto::AccumulatingOperation for BoringEcAgreeOperation { } #[cfg(not(soong))] Key::X25519(_) => Err(km_err!(UnsupportedEcCurve, "X25519 not supported in cargo")), - Key::Ed25519(_) => { - Err(km_err!(IncompatibleAlgorithm, "Ed25519 key not valid for agreement")) - } + Key::Ed25519(_) => Err(km_err!( + IncompatibleAlgorithm, + "Ed25519 key not valid for agreement" + )), } } } @@ -322,7 +359,11 @@ impl crypto::AccumulatingOperation for BoringEcDigestSignOperation { fn update(&mut self, data: &[u8]) -> Result<(), Error> { // Safety: `data` is a valid slice, and `self.md_ctx` is non-`nullptr` and valid. unsafe { - cvt(ffi::EVP_DigestUpdate(self.md_ctx.0, data.as_ptr() as *const _, data.len()))?; + cvt(ffi::EVP_DigestUpdate( + self.md_ctx.0, + data.as_ptr() as *const _, + data.len(), + ))?; } Ok(()) } @@ -331,7 +372,11 @@ impl crypto::AccumulatingOperation for BoringEcDigestSignOperation { let mut max_siglen = 0; // Safety: `self.md_ctx` is non-`nullptr` and valid. unsafe { - cvt(ffi::EVP_DigestSignFinal(self.md_ctx.0, ptr::null_mut(), &mut max_siglen))?; + cvt(ffi::EVP_DigestSignFinal( + self.md_ctx.0, + ptr::null_mut(), + &mut max_siglen, + ))?; } let mut buf = vec_try![0; max_siglen]?; let mut actual_siglen = max_siglen; @@ -361,7 +406,11 @@ impl BoringEcUndigestSignOperation { let group = nist_curve_to_group(curve)?; let ec_key = ossl!(private_key_from_der_for_group(&key.0, group.as_ref()))?; // Input to an undigested EC signing operation must be smaller than key size. - Ok(Self { ec_key, pending_input: Vec::new(), max_size: curve.coord_len() }) + Ok(Self { + ec_key, + pending_input: Vec::new(), + max_size: curve.coord_len(), + }) } } @@ -380,7 +429,10 @@ impl crypto::AccumulatingOperation for BoringEcUndigestSignOperation { fn finish(self: Box) -> Result, Error> { // BoringSSL doesn't support `EVP_PKEY` use without digest, so use low-level ECDSA // functionality. - let sig = ossl!(openssl::ecdsa::EcdsaSig::sign(&self.pending_input, &self.ec_key))?; + let sig = ossl!(openssl::ecdsa::EcdsaSig::sign( + &self.pending_input, + &self.ec_key + ))?; let sig = ossl!(sig.to_der())?; Ok(sig) } @@ -398,7 +450,10 @@ impl BoringEd25519SignOperation { &key.0, openssl::pkey::Id::ED25519 ))?; - Ok(Self { pkey, pending_input: Vec::new() }) + Ok(Self { + pkey, + pending_input: Vec::new(), + }) } } @@ -442,7 +497,10 @@ fn nist_key_to_group(key: &ec::Key) -> Result { ec::Key::P384(_) => Nid::SECP384R1, ec::Key::P521(_) => Nid::SECP521R1, ec::Key::Ed25519(_) | ec::Key::X25519(_) => { - return Err(km_err!(UnsupportedEcCurve, "no NIST group for curve25519 key")) + return Err(km_err!( + UnsupportedEcCurve, + "no NIST group for curve25519 key" + )) } }) .map_err(openssl_err!("failed to determine EcGroup")) diff --git a/libs/rust/boringssl/src/error.rs b/libs/rust/boringssl/src/error.rs new file mode 100644 index 0000000..0c711b3 --- /dev/null +++ b/libs/rust/boringssl/src/error.rs @@ -0,0 +1,101 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module implements Error for the keystore2_crypto library. +use crate::zvec; + +/// Crypto specific error codes. +#[derive(Debug, thiserror::Error, Eq, PartialEq)] +pub enum Error { + /// This is returned if the C/C++ implementation of AES_gcm_decrypt returned false. + #[error("Failed to decrypt.")] + DecryptionFailed, + + /// This is returned if the C/C++ implementation of AES_gcm_encrypt returned false. + #[error("Failed to encrypt.")] + EncryptionFailed, + + /// The initialization vector has the wrong length. + #[error("Invalid IV length.")] + InvalidIvLength, + + /// The aead tag has the wrong length. + #[error("Invalid AEAD tag length.")] + InvalidAeadTagLength, + + /// The key has the wrong length. + #[error("Invalid key length.")] + InvalidKeyLength, + + /// Invalid data length. + #[error("Invalid data length.")] + InvalidDataLength, + + /// Invalid salt length. + #[error("Invalid salt length.")] + InvalidSaltLength, + + /// Random number generation failed. + #[error("Random number generation failed.")] + RandomNumberGenerationFailed, + + /// Nix error. + #[error(transparent)] + NixError(#[from] nix::Error), + + /// This is returned if the C implementation of HKDFExtract returned false + /// or otherwise failed. + #[error("Failed to extract.")] + HKDFExtractFailed, + + /// This is returned if the C implementation of HKDFExpand returned false. + #[error("Failed to expand.")] + HKDFExpandFailed, + + /// This is returned if the C implementation of ECDHComputeKey returned -1. + #[error("Failed to compute ecdh key.")] + ECDHComputeKeyFailed, + + /// This is returned if the C implementation of ECKEYGenerateKey returned null. + #[error("Failed to generate key.")] + ECKEYGenerateKeyFailed, + + /// This is returned if the C implementation of ECKEYMarshalPrivateKey returned 0. + #[error("Failed to marshal private key.")] + ECKEYMarshalPrivateKeyFailed, + + /// This is returned if the C implementation of ECKEYParsePrivateKey returned null. + #[error("Failed to parse private key.")] + ECKEYParsePrivateKeyFailed, + + /// This is returned if the C implementation of ECPOINTPoint2Oct returned 0. + #[error("Failed to convert point to oct.")] + ECPoint2OctFailed, + + /// This is returned if the C implementation of ECPOINTOct2Point returned null. + #[error("Failed to convert oct to point.")] + ECOct2PointFailed, + + /// This is returned if the C implementation of extractSubjectFromCertificate failed. + #[error("Failed to extract certificate subject.")] + ExtractSubjectFailed, + + /// This is returned if the C implementation of hmacSha256 failed. + #[error("Failed to calculate HMAC-SHA256.")] + HmacSha256Failed, + + /// Zvec error. + #[error(transparent)] + ZVec(#[from] zvec::Error), +} diff --git a/libs/rust/boringssl/src/hmac.rs b/libs/rust/boringssl/src/hmac.rs index 3f8de9a..d6df2b2 100644 --- a/libs/rust/boringssl/src/hmac.rs +++ b/libs/rust/boringssl/src/hmac.rs @@ -15,13 +15,15 @@ //! BoringSSL-based implementation of HMAC. use crate::types::HmacCtx; use crate::{malloc_err, openssl_last_err}; -use Box; -use Vec; +use anyhow::Result; #[cfg(soong)] use bssl_sys as ffi; +use kmr_common::crypto::Hkdf; use kmr_common::{crypto, crypto::OpaqueOr, explicit, km_err, vec_try, Error}; use kmr_wire::keymint::Digest; use log::error; +use Box; +use Vec; /// [`crypto::Hmac`] implementation based on BoringSSL. pub struct BoringHmac; @@ -130,3 +132,16 @@ fn digest_into_openssl_ffi(digest: Digest) -> Result<*const ffi::EVP_MD, Error> } } } + +pub fn hkdf_expand(out_len: usize, key: &[u8], info: &[u8]) -> Result> { + let hmac = BoringHmac; + let key = crypto::hmac::Key(key.to_vec()); + let key: OpaqueOr = OpaqueOr::Explicit(key); + + let result = hmac.expand(&key, info, out_len); + + match result { + Ok(v) => Ok(v), + Err(e) => Err(anyhow::anyhow!("hkdf expand error:0 {:?}", e)), + } +} diff --git a/libs/rust/boringssl/src/km.rs b/libs/rust/boringssl/src/km.rs new file mode 100644 index 0000000..3b65d9d --- /dev/null +++ b/libs/rust/boringssl/src/km.rs @@ -0,0 +1,526 @@ +use std::{marker::PhantomData, vec}; + +use crate::{error::Error, zvec::ZVec}; +use ffi::{EC_KEY_free, EC_KEY, EC_POINT, EVP_MAX_MD_SIZE}; +use foreign_types::ForeignType; +use kmr_common::crypto::Rng; +use openssl::{ + ec::{EcGroup, EcKey, EcPoint}, + hash::MessageDigest, + nid::Nid, + pkcs5::pbkdf2_hmac, + pkey::Private, + symm::{Cipher, Crypter, Mode}, +}; + +/// Length of the expected initialization vector. +pub const GCM_IV_LENGTH: usize = 12; +/// Length of the expected AEAD TAG. +pub const TAG_LENGTH: usize = 16; +/// Length of an AES 256 key in bytes. +pub const AES_256_KEY_LENGTH: usize = 32; +/// Length of an AES 128 key in bytes. +pub const AES_128_KEY_LENGTH: usize = 16; +/// Length of the expected salt for key from password generation. +pub const SALT_LENGTH: usize = 16; +/// Length of an HMAC-SHA256 tag in bytes. +pub const HMAC_SHA256_LEN: usize = 32; +/// Length of the GCM tag in bytes. +pub const GCM_TAG_LENGTH: usize = 128 / 8; + +/// Older versions of keystore produced IVs with four extra +/// ignored zero bytes at the end; recognise and trim those. +pub const LEGACY_IV_LENGTH: usize = 16; + +/// Generate an AES256 key, essentially 32 random bytes from the underlying +/// boringssl library discretely stuffed into a ZVec. +pub fn generate_aes256_key() -> Result { + let mut key = ZVec::new(AES_256_KEY_LENGTH)?; + // Safety: key has the same length as the requested number of random bytes. + if randomBytes(key.as_mut_ptr(), AES_256_KEY_LENGTH) { + Ok(key) + } else { + Err(Error::RandomNumberGenerationFailed) + } +} + +/// Generate a salt. +pub fn generate_salt() -> Result, Error> { + generate_random_data(SALT_LENGTH) +} + +/// Generate random data of the given size. +pub fn generate_random_data(size: usize) -> Result, Error> { + let mut data = vec![0; size]; + // Safety: data has the same length as the requested number of random bytes. + if randomBytes(data.as_mut_ptr(), size) { + Ok(data) + } else { + Err(Error::RandomNumberGenerationFailed) + } +} + +#[allow(non_snake_case)] +fn randomBytes(buf: *mut u8, len: usize) -> bool { + let mut rng = crate::rng::BoringRng::default(); + rng.fill_bytes(unsafe { std::slice::from_raw_parts_mut(buf, len) }); + true +} + +/// Perform HMAC-SHA256. +pub fn hmac_sha256(key: &[u8], msg: &[u8]) -> Result, Error> { + let mut tag = vec![0; HMAC_SHA256_LEN]; + // Safety: The first two pairs of arguments must point to const buffers with + // size given by the second arg of the pair. The final pair of arguments + // must point to an output buffer with size given by the second arg of the + // pair. + let digest = unsafe { ffi::EVP_sha256() }; + let mut out_len = tag.len() as u32; + let result = unsafe { + ffi::HMAC( + digest, + key.as_ptr() as *const std::ffi::c_void, + key.len() as i32, + msg.as_ptr(), + msg.len(), + tag.as_mut_ptr(), + &mut out_len, + ) + }; + if !result.is_null() { + Ok(tag) + } else { + Err(Error::HmacSha256Failed) + } +} + +/// Uses AES GCM to decipher a message given an initialization vector, aead tag, and key. +/// This function accepts 128 and 256-bit keys and uses AES128 and AES256 respectively based +/// on the key length. +/// This function returns the plaintext message in a ZVec because it is assumed that +/// it contains sensitive information that should be zeroed from memory before its buffer is +/// freed. Input key is taken as a slice for flexibility, but it is recommended that it is held +/// in a ZVec as well. +pub fn aes_gcm_decrypt(data: &[u8], iv: &[u8], tag: &[u8], key: &[u8]) -> Result { + // Old versions of aes_gcm_encrypt produced 16 byte IVs, but the last four bytes were ignored + // so trim these to the correct size. + let iv = match iv.len() { + GCM_IV_LENGTH => iv, + LEGACY_IV_LENGTH => &iv[..GCM_IV_LENGTH], + _ => return Err(Error::InvalidIvLength), + }; + if tag.len() != TAG_LENGTH { + return Err(Error::InvalidAeadTagLength); + } + + match key.len() { + AES_128_KEY_LENGTH | AES_256_KEY_LENGTH => {} + _ => return Err(Error::InvalidKeyLength), + } + + let mut result = vec![0; data.len()]; + + // Safety: The first two arguments must point to buffers with a size given by the third + // argument. We pass the length of the key buffer along with the key. + // The `iv` buffer must be 12 bytes and the `tag` buffer 16, which we check above. + match AES_gcm_decrypt(data, result.as_mut_slice(), key, iv, tag) { + true => Ok(ZVec::try_from(result.as_slice())?), + false => Err(Error::DecryptionFailed), + } +} + +#[allow(non_snake_case)] +fn AES_gcm_decrypt(input: &[u8], output: &mut [u8], key: &[u8], iv: &[u8], tag: &[u8]) -> bool { + let cipher = match key.len() { + 16 => Cipher::aes_128_gcm(), + 32 => Cipher::aes_256_gcm(), + _ => return false, + }; + + let mut crypter = match Crypter::new(cipher, Mode::Decrypt, key, Some(iv)) { + Ok(c) => c, + Err(_) => return false, + }; + + crypter.pad(false); + + if crypter.set_tag(tag).is_err() { + return false; + } + + let mut decrypted = vec![0u8; input.len() + cipher.block_size()]; + + let count = match crypter.update(input, &mut decrypted) { + Ok(count) => count, + Err(_) => return false, + }; + + let final_count = match crypter.finalize(&mut decrypted[count..]) { + Ok(count) => count, + Err(_) => return false, // 标签验证失败或其他错误 + }; + + let total_count = count + final_count; + + if total_count != input.len() { + return false; + } + + output[..total_count].copy_from_slice(&decrypted[..total_count]); + + true +} + +/// Uses AES GCM to encrypt a message given a key. +/// This function accepts 128 and 256-bit keys and uses AES128 and AES256 respectively based on +/// the key length. The function generates an initialization vector. The return value is a tuple +/// of `(ciphertext, iv, tag)`. +pub fn aes_gcm_encrypt(plaintext: &[u8], key: &[u8]) -> Result<(Vec, Vec, Vec), Error> { + let mut iv = vec![0; GCM_IV_LENGTH]; + // Safety: iv is GCM_IV_LENGTH bytes long. + if !randomBytes(iv.as_mut_ptr(), GCM_IV_LENGTH) { + return Err(Error::RandomNumberGenerationFailed); + } + + match key.len() { + AES_128_KEY_LENGTH | AES_256_KEY_LENGTH => {} + _ => return Err(Error::InvalidKeyLength), + } + + let mut ciphertext: Vec = vec![0; plaintext.len()]; + let mut tag: Vec = vec![0; TAG_LENGTH]; + // Safety: The first two arguments must point to buffers with a size given by the third + // argument. We pass the length of the key buffer along with the key. + // The `iv` buffer must be 12 bytes and the `tag` buffer 16, which we check above. + match AES_gcm_encrypt(plaintext, &mut ciphertext, key, &iv, &mut tag) { + Ok(()) => Ok((ciphertext, iv, tag)), + Err(_) => Err(Error::EncryptionFailed), + } +} + +#[allow(non_snake_case)] +fn AES_gcm_encrypt( + input: &[u8], + output: &mut [u8], + key: &[u8], + iv: &[u8], + tag: &mut [u8], +) -> Result<(), openssl::error::ErrorStack> { + let cipher = match key.len() { + 16 => Cipher::aes_128_gcm(), + 32 => Cipher::aes_256_gcm(), + _ => return Err(openssl::error::ErrorStack::get()), + }; + + let mut crypter = Crypter::new(cipher, Mode::Encrypt, key, Some(iv))?; + + crypter.pad(false); + + let mut encrypted = vec![0u8; input.len() + cipher.block_size()]; + let mut count = crypter.update(input, &mut encrypted)?; + count += crypter.finalize(&mut encrypted[count..])?; + + encrypted.truncate(count); + + if encrypted.len() != input.len() { + return Err(openssl::error::ErrorStack::get()); + } + + output[..encrypted.len()].copy_from_slice(&encrypted); + + crypter.get_tag(tag)?; + + Ok(()) +} + +fn pbkdf2(key: &mut [u8], password: &[u8], salt: &[u8]) -> Result<(), openssl::error::ErrorStack> { + let iterations = 8192; + + let digest = if key.len() == AES_128_KEY_LENGTH { + MessageDigest::sha1() + } else { + MessageDigest::sha256() + }; + + pbkdf2_hmac(password, salt, iterations, digest, key) +} + +/// A high-entropy synthetic password from which an AES key may be derived. +pub enum Password<'a> { + /// Borrow an existing byte array + Ref(&'a [u8]), + /// Use an owned ZVec to store the key + Owned(ZVec), +} + +impl<'a> From<&'a [u8]> for Password<'a> { + fn from(pw: &'a [u8]) -> Self { + Self::Ref(pw) + } +} + +impl<'a> Password<'a> { + fn get_key(&'a self) -> &'a [u8] { + match self { + Self::Ref(b) => b, + Self::Owned(z) => z, + } + } + + /// Derives a key from the given password and salt, using PBKDF2 with 8192 iterations. + /// + /// The salt length must be 16 bytes, and the output key length must be 16 or 32 bytes. + /// + /// This function exists only for backwards compatibility reasons. Keystore now receives only + /// high-entropy synthetic passwords, which do not require key stretching. + pub fn derive_key_pbkdf2(&self, salt: &[u8], out_len: usize) -> Result { + if salt.len() != SALT_LENGTH { + return Err(Error::InvalidSaltLength); + } + match out_len { + AES_128_KEY_LENGTH | AES_256_KEY_LENGTH => {} + _ => return Err(Error::InvalidKeyLength), + } + + let pw = self.get_key(); + let mut result = vec![0; out_len]; + + // Call pbkdf2 with the correct arguments. + pbkdf2(result.as_mut_slice(), pw, salt).map_err(|_| Error::EncryptionFailed)?; + + Ok(ZVec::try_from(result.as_slice())?) + } + + /// Derives a key from the given high-entropy synthetic password and salt, using HKDF. + pub fn derive_key_hkdf(&self, salt: &[u8], out_len: usize) -> Result { + let prk = hkdf_extract(self.get_key(), salt)?; + let info = []; + hkdf_expand(out_len, &prk, &info) + } + + /// Try to make another Password object with the same data. + pub fn try_clone(&self) -> Result, Error> { + Ok(Password::Owned(ZVec::try_from(self.get_key())?)) + } +} + +/// Calls the boringssl HKDF_extract function. +pub fn hkdf_extract(secret: &[u8], salt: &[u8]) -> Result { + let max_size: usize = EVP_MAX_MD_SIZE.try_into().unwrap(); + let mut buf = vec![0; max_size]; + + let mut out_len = 0; + // Safety: HKDF_extract writes at most EVP_MAX_MD_SIZE bytes. + // Secret and salt point to valid buffers. + let result = { hkdf_extract_rs(buf.as_mut_slice(), &mut out_len, secret, salt) }; + if !result { + return Err(Error::HKDFExtractFailed); + } + // According to the boringssl API, this should never happen. + if out_len > max_size { + return Err(Error::HKDFExtractFailed); + } + // HKDF_extract may write fewer than the maximum number of bytes, so we + // truncate the buffer. + let mut buf = ZVec::try_from(buf)?; + buf.reduce_len(out_len); + Ok(buf) +} + +fn hkdf_extract_rs(out_key: &mut [u8], out_len: &mut usize, secret: &[u8], salt: &[u8]) -> bool { + let digest = MessageDigest::sha256(); + + match openssl::pkcs5::pbkdf2_hmac(secret, salt, 1, digest, out_key) { + Ok(_) => { + *out_len = out_key.len(); + true + } + Err(_) => false, + } +} + +/// Calls the boringssl HKDF_expand function. +pub fn hkdf_expand(out_len: usize, prk: &[u8], info: &[u8]) -> Result { + let mut buf = vec![0; out_len]; + // Safety: HKDF_expand writes out_len bytes to the buffer. + // prk and info are valid buffers. + let result = hkdf_expand_rs(buf.as_mut_slice(), prk, info); + if !result { + return Err(Error::HKDFExpandFailed); + } + Ok(ZVec::try_from(buf.as_slice())?) +} + +fn hkdf_expand_rs(out_key: &mut [u8], prk: &[u8], info: &[u8]) -> bool { + let digest = MessageDigest::sha256(); + + match openssl::pkcs5::pbkdf2_hmac(prk, info, 1, digest, out_key) { + Ok(_) => true, + Err(_) => false, + } +} + +/// A wrapper around the boringssl EC_KEY type that frees it on drop. +/// TODO: replace with openssl::ec::EcKey when ForeignType is implemented for it. +/// I don't replace it for now because it just works for now. +pub struct ECKey(*mut EC_KEY); + +impl Drop for ECKey { + fn drop(&mut self) { + // Safety: We only create ECKey objects for valid EC_KEYs + // and they are the sole owners of those keys. + unsafe { EC_KEY_free(self.0) }; + } +} + +// Wrappers around the boringssl EC_POINT type. +// The EC_POINT can either be owned (and therefore mutable) or a pointer to an +// EC_POINT owned by someone else (and thus immutable). The former are freed +// on drop. + +/// An owned EC_POINT object. +pub struct OwnedECPoint(*mut EC_POINT); + +/// A pointer to an EC_POINT object. +pub struct BorrowedECPoint<'a> { + data: *const EC_POINT, + phantom: PhantomData<&'a EC_POINT>, +} + +impl OwnedECPoint { + /// Get the wrapped EC_POINT object. + pub fn get_point(&self) -> &EC_POINT { + // Safety: We only create OwnedECPoint objects for valid EC_POINTs. + unsafe { self.0.as_ref().unwrap() } + } +} + +impl BorrowedECPoint<'_> { + /// Get the wrapped EC_POINT object. + pub fn get_point(&self) -> &EC_POINT { + // Safety: We only create BorrowedECPoint objects for valid EC_POINTs. + unsafe { self.data.as_ref().unwrap() } + } +} + +impl Drop for OwnedECPoint { + fn drop(&mut self) { + // Safety: We only create OwnedECPoint objects for valid + // EC_POINTs and they are the sole owners of those points. + unsafe { ffi::EC_POINT_free(self.0) }; + } +} + +const EC_MAX_BYTES: usize = 133; // 1 + 2 * field_elem_size for P-256 + +/// Calls the boringssl ECDH_compute_key function. +pub fn ecdh_compute_key(pub_key: &EC_POINT, priv_key: &ECKey) -> Result { + let mut buf = ZVec::new(EC_MAX_BYTES)?; + let result = + // Safety: Our ECDHComputeKey wrapper passes EC_MAX_BYES to ECDH_compute_key, which + // writes at most that many bytes to the output. + // The two keys are valid objects. + unsafe { ffi::ECDH_compute_key(buf.as_mut_ptr() as *mut std::ffi::c_void, EC_MAX_BYTES, pub_key, priv_key.0, None) }; + if result == -1 { + return Err(Error::ECDHComputeKeyFailed); + } + let out_len = result.try_into().unwrap(); + // According to the boringssl API, this should never happen. + if out_len > buf.len() { + return Err(Error::ECDHComputeKeyFailed); + } + // ECDH_compute_key may write fewer than the maximum number of bytes, so we + // truncate the buffer. + buf.reduce_len(out_len); + Ok(buf) +} + +/// Calls the boringssl EC_KEY_generate_key function. +pub fn ec_key_generate_key() -> Result { + let group = + EcGroup::from_curve_name(Nid::SECP521R1).map_err(|_| Error::ECKEYGenerateKeyFailed)?; + let ec_key: EcKey = + EcKey::generate(&group).map_err(|_| Error::ECKEYGenerateKeyFailed)?; + + Ok(ECKey(ec_key.as_ptr())) +} + +/// Calls the boringssl EC_KEY_marshal_private_key function. +pub fn ec_key_marshal_private_key(key: &ECKey) -> Result { + let len = 73; // Empirically observed length of private key + let mut buf = vec![0; len]; + // Safety: the key is valid. + // This will not write past the specified length of the buffer; if the + // len above is too short, it returns 0. + let priv_key: EcKey = unsafe { EcKey::from_ptr(key.0) }; + let der = priv_key + .private_key_to_der() + .map_err(|_| Error::ECKEYMarshalPrivateKeyFailed)?; + + if buf.len() < der.len() { + return Err(Error::ECKEYMarshalPrivateKeyFailed); + } + buf.as_mut_slice()[..der.len()].copy_from_slice(&der); + Ok(ZVec::try_from(buf.as_slice())?) +} + +/// Calls the boringssl EC_KEY_parse_private_key function. +pub fn ec_key_parse_private_key(buf: &[u8]) -> Result { + // Safety: this will not read past the specified length of the buffer. + // It fails if less than the whole buffer is consumed. + let group = + EcGroup::from_curve_name(Nid::SECP521R1).map_err(|_| Error::ECKEYParsePrivateKeyFailed)?; + let priv_key = + EcKey::private_key_from_der(buf).map_err(|_| Error::ECKEYParsePrivateKeyFailed)?; + + if priv_key.group().curve_name() != group.curve_name() { + return Err(Error::ECKEYParsePrivateKeyFailed); + } + + Ok(ECKey(priv_key.as_ptr())) +} + +/// Calls the boringssl EC_KEY_get0_public_key function. +pub fn ec_key_get0_public_key(key: &ECKey) -> BorrowedECPoint<'_> { + // Safety: The key is valid. + // This returns a pointer to a key, so we create an immutable variant. + BorrowedECPoint { + data: unsafe { ffi::EC_KEY_get0_public_key(key.0) }, + phantom: PhantomData, + } +} + +pub fn ec_point_point_to_oct(point: &EC_POINT) -> Result, Error> { + let ec_point = unsafe { EcPoint::from_ptr(point as *const EC_POINT as *mut EC_POINT) }; + let group = EcGroup::from_curve_name(Nid::SECP521R1).map_err(|_| Error::ECPoint2OctFailed)?; + + // We fix the length to 133 (1 + 2 * field_elem_size), as we get an error if it's too small. + let len = 133; + let mut buf = vec![0; len]; + + let mut ctx = openssl::bn::BigNumContext::new().map_err(|_| Error::ECPoint2OctFailed)?; + let bytes = ec_point + .to_bytes( + &group, + openssl::ec::PointConversionForm::UNCOMPRESSED, + &mut ctx, + ) + .map_err(|_| Error::ECPoint2OctFailed)?; + if buf.len() < bytes.len() { + return Err(Error::ECPoint2OctFailed); + } + buf.as_mut_slice()[..bytes.len()].copy_from_slice(&bytes); + + Ok(buf) +} + +/// Calls the boringssl EC_POINT_oct2point function. +pub fn ec_point_oct_to_point(buf: &[u8]) -> Result { + // Safety: The buffer is valid. + let group = EcGroup::from_curve_name(Nid::SECP521R1).map_err(|_| Error::ECPoint2OctFailed)?; + let mut ctx = openssl::bn::BigNumContext::new().map_err(|_| Error::ECPoint2OctFailed)?; + let ec_point = + EcPoint::from_bytes(&group, buf, &mut ctx).map_err(|_| Error::ECPoint2OctFailed)?; + + Ok(OwnedECPoint(ec_point.as_ptr())) +} diff --git a/libs/rust/boringssl/src/lib.rs b/libs/rust/boringssl/src/lib.rs index 6684545..556dbef 100644 --- a/libs/rust/boringssl/src/lib.rs +++ b/libs/rust/boringssl/src/lib.rs @@ -26,10 +26,13 @@ pub mod aes; pub mod des; pub mod ec; pub mod eq; +pub mod error; pub mod hmac; +pub mod km; pub mod rng; pub mod rsa; pub mod sha256; +pub mod zvec; #[cfg(soong)] mod err; diff --git a/libs/rust/boringssl/src/rng.rs b/libs/rust/boringssl/src/rng.rs index 88d672f..2987f11 100644 --- a/libs/rust/boringssl/src/rng.rs +++ b/libs/rust/boringssl/src/rng.rs @@ -26,7 +26,10 @@ impl crypto::Rng for BoringRng { #[cfg(soong)] // Safety: `data` is a valid slice. unsafe { - ffi::RAND_seed(data.as_ptr() as *const libc::c_void, data.len() as libc::c_int); + ffi::RAND_seed( + data.as_ptr() as *const libc::c_void, + data.len() as libc::c_int, + ); } #[cfg(not(soong))] // Safety: `data` is a valid slice. diff --git a/libs/rust/boringssl/src/rsa.rs b/libs/rust/boringssl/src/rsa.rs index bb0181d..49576fc 100644 --- a/libs/rust/boringssl/src/rsa.rs +++ b/libs/rust/boringssl/src/rsa.rs @@ -15,8 +15,6 @@ //! BoringSSL-based implementation of RSA. use crate::types::{EvpMdCtx, EvpPkeyCtx}; use crate::{cvt, cvt_p, digest_into_openssl, openssl_err, openssl_last_err, ossl}; -use Box; -use Vec; #[cfg(soong)] use bssl_sys as ffi; use core::ptr; @@ -28,6 +26,8 @@ use kmr_common::crypto::{ use kmr_common::{crypto, explicit, km_err, vec_try, Error, FallibleAllocExt}; use kmr_wire::{keymint, keymint::Digest, KeySizeInBits, RsaExponent}; use openssl::hash::MessageDigest; +use Box; +use Vec; /// Smallest allowed public exponent. const MIN_RSA_EXPONENT: RsaExponent = RsaExponent(3); @@ -41,7 +41,9 @@ pub struct BoringRsa { impl core::default::Default for BoringRsa { fn default() -> Self { ffi::init(); - Self { _priv: core::marker::PhantomData } + Self { + _priv: core::marker::PhantomData, + } } } @@ -69,8 +71,9 @@ impl crypto::Rsa for BoringRsa { pub_exponent )); } - let exponent = openssl::bn::BigNum::from_slice(&pub_exponent.0.to_be_bytes()[..]) - .map_err(openssl_err!("failed to create BigNum for exponent {:?}", pub_exponent))?; + let exponent = openssl::bn::BigNum::from_slice(&pub_exponent.0.to_be_bytes()[..]).map_err( + openssl_err!("failed to create BigNum for exponent {:?}", pub_exponent), + )?; let rsa_key = openssl::rsa::Rsa::generate_with_e(key_size.0, &exponent).map_err(openssl_err!( @@ -89,7 +92,12 @@ impl crypto::Rsa for BoringRsa { ) -> Result, Error> { let key = explicit!(key)?; let max_size = key.size(); - Ok(Box::new(BoringRsaDecryptOperation { key, mode, pending_input: Vec::new(), max_size })) + Ok(Box::new(BoringRsaDecryptOperation { + key, + mode, + pending_input: Vec::new(), + max_size, + })) } fn begin_sign( @@ -110,9 +118,15 @@ impl crypto::Rsa for BoringRsa { } SignMode::Pkcs1_1_5Padding(digest) | SignMode::PssPadding(digest) => { if let Some(digest) = digest_into_openssl(digest) { - Ok(Box::new(BoringRsaDigestSignOperation::new(key, mode, digest, padding)?)) + Ok(Box::new(BoringRsaDigestSignOperation::new( + key, mode, digest, padding, + )?)) } else { - Err(km_err!(InvalidArgument, "no digest provided for mode {:?}", mode)) + Err(km_err!( + InvalidArgument, + "no digest provided for mode {:?}", + mode + )) } } } @@ -144,21 +158,33 @@ impl crypto::AccumulatingOperation for BoringRsaDecryptOperation { let padding = match self.mode { DecryptionMode::NoPadding => openssl::rsa::Padding::NONE, - DecryptionMode::OaepPadding { msg_digest: _, mgf_digest: _ } => { - openssl::rsa::Padding::PKCS1_OAEP - } + DecryptionMode::OaepPadding { + msg_digest: _, + mgf_digest: _, + } => openssl::rsa::Padding::PKCS1_OAEP, DecryptionMode::Pkcs1_1_5Padding => openssl::rsa::Padding::PKCS1, }; - decrypter - .set_rsa_padding(padding) - .map_err(openssl_err!("failed to create set_rsa_padding for {:?}", self.mode))?; - - if let DecryptionMode::OaepPadding { msg_digest, mgf_digest } = self.mode { + decrypter.set_rsa_padding(padding).map_err(openssl_err!( + "failed to create set_rsa_padding for {:?}", + self.mode + ))?; + + if let DecryptionMode::OaepPadding { + msg_digest, + mgf_digest, + } = self.mode + { let omsg_digest = digest_into_openssl(msg_digest).ok_or_else(|| { - km_err!(UnsupportedDigest, "Digest::None not allowed for RSA-OAEP msg digest") + km_err!( + UnsupportedDigest, + "Digest::None not allowed for RSA-OAEP msg digest" + ) })?; let omgf_digest = digest_into_openssl(mgf_digest).ok_or_else(|| { - km_err!(UnsupportedDigest, "Digest::None not allowed for RSA-OAEP MGF1 digest") + km_err!( + UnsupportedDigest, + "Digest::None not allowed for RSA-OAEP MGF1 digest" + ) })?; decrypter .set_rsa_oaep_md(omsg_digest) @@ -240,7 +266,10 @@ impl BoringRsaDigestSignOperation { } // Safety: `op.pctx` is not `nullptr` and is valid. - cvt(ffi::EVP_PKEY_CTX_set_rsa_padding(op.pctx.0, padding.as_raw()))?; + cvt(ffi::EVP_PKEY_CTX_set_rsa_padding( + op.pctx.0, + padding.as_raw(), + ))?; if let SignMode::PssPadding(digest) = mode { let digest_len = (kmr_common::tag::digest_len(digest)? / 8) as libc::c_int; @@ -259,7 +288,11 @@ impl crypto::AccumulatingOperation for BoringRsaDigestSignOperation { fn update(&mut self, data: &[u8]) -> Result<(), Error> { // Safety: `data` is a valid slice, and `self.md_ctx` is non-`nullptr` and valid. unsafe { - cvt(ffi::EVP_DigestUpdate(self.md_ctx.0, data.as_ptr() as *const _, data.len()))?; + cvt(ffi::EVP_DigestUpdate( + self.md_ctx.0, + data.as_ptr() as *const _, + data.len(), + ))?; } Ok(()) } @@ -268,7 +301,11 @@ impl crypto::AccumulatingOperation for BoringRsaDigestSignOperation { let mut max_siglen = 0; // Safety: `self.md_ctx` is non-`nullptr` and valid. unsafe { - cvt(ffi::EVP_DigestSignFinal(self.md_ctx.0, ptr::null_mut(), &mut max_siglen))?; + cvt(ffi::EVP_DigestSignFinal( + self.md_ctx.0, + ptr::null_mut(), + &mut max_siglen, + ))?; } let mut buf = vec_try![0; max_siglen]?; let mut actual_siglen = max_siglen; @@ -299,12 +336,24 @@ impl BoringRsaUndigestSignOperation { let rsa_key = ossl!(openssl::rsa::Rsa::private_key_from_der(&key.0))?; let (left_pad, max_size) = match mode { SignMode::NoPadding => (true, rsa_key.size() as usize), - SignMode::Pkcs1_1_5Padding(Digest::None) => { - (false, (rsa_key.size() as usize) - PKCS1_UNDIGESTED_SIGNATURE_PADDING_OVERHEAD) + SignMode::Pkcs1_1_5Padding(Digest::None) => ( + false, + (rsa_key.size() as usize) - PKCS1_UNDIGESTED_SIGNATURE_PADDING_OVERHEAD, + ), + _ => { + return Err(km_err!( + UnsupportedPaddingMode, + "sign undigested mode {:?}", + mode + )) } - _ => return Err(km_err!(UnsupportedPaddingMode, "sign undigested mode {:?}", mode)), }; - Ok(Self { rsa_key, left_pad, pending_input: Vec::new(), max_size }) + Ok(Self { + rsa_key, + left_pad, + pending_input: Vec::new(), + max_size, + }) } } diff --git a/libs/rust/boringssl/src/zvec.rs b/libs/rust/boringssl/src/zvec.rs new file mode 100644 index 0000000..e7e6ba6 --- /dev/null +++ b/libs/rust/boringssl/src/zvec.rs @@ -0,0 +1,150 @@ +// 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. + +//! Implements ZVec, a vector that is mlocked during its lifetime and zeroed +//! when dropped. + +use nix::sys::mman::{mlock, munlock}; +use std::convert::TryFrom; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::ptr::write_volatile; +use std::ptr::NonNull; + +/// A semi fixed size u8 vector that is zeroed when dropped. It can shrink in +/// size but cannot grow larger than the original size (and if it shrinks it +/// still owns the entire buffer). Also the data is pinned in memory with +/// mlock. +#[derive(Default, Eq, PartialEq)] +pub struct ZVec { + elems: Box<[u8]>, + len: usize, +} + +/// ZVec specific error codes. +#[derive(Debug, thiserror::Error, Eq, PartialEq)] +pub enum Error { + /// Underlying libc error. + #[error(transparent)] + NixError(#[from] nix::Error), +} + +impl ZVec { + /// Create a ZVec with the given size. + pub fn new(size: usize) -> Result { + let v: Vec = vec![0; size]; + let b = v.into_boxed_slice(); + if size > 0 { + // SAFETY: The address range is part of our address space. + unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?; + } + Ok(Self { + elems: b, + len: size, + }) + } + + /// Reduce the length to the given value. Does nothing if that length is + /// greater than the length of the vector. Note that it still owns the + /// original allocation even if the length is reduced. + pub fn reduce_len(&mut self, len: usize) { + if len <= self.elems.len() { + self.len = len; + } + } + + /// Attempts to make a clone of the Zvec. This may fail due trying to mlock + /// the new memory region. + pub fn try_clone(&self) -> Result { + let mut result = Self::new(self.len())?; + result[..].copy_from_slice(&self[..]); + Ok(result) + } +} + +impl Drop for ZVec { + fn drop(&mut self) { + for i in 0..self.elems.len() { + // SAFETY: The pointer is valid and properly aligned because it came from a reference. + unsafe { write_volatile(&mut self.elems[i], 0) }; + } + if !self.elems.is_empty() { + if let Err(e) = + // SAFETY: The address range is part of our address space, and was previously locked + // by `mlock` in `ZVec::new` or the `TryFrom>` implementation. + unsafe { munlock(NonNull::from(&self.elems).cast(), self.elems.len()) } + { + log::error!("In ZVec::drop: `munlock` failed: {:?}.", e); + } + } + } +} + +impl Deref for ZVec { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.elems[0..self.len] + } +} + +impl DerefMut for ZVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.elems[0..self.len] + } +} + +impl fmt::Debug for ZVec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.elems.is_empty() { + write!(f, "Zvec empty") + } else { + write!( + f, + "Zvec size: {} [ Sensitive information redacted ]", + self.len + ) + } + } +} + +impl TryFrom<&[u8]> for ZVec { + type Error = Error; + + fn try_from(v: &[u8]) -> Result { + let mut z = ZVec::new(v.len())?; + if !v.is_empty() { + z.clone_from_slice(v); + } + Ok(z) + } +} + +impl TryFrom> for ZVec { + type Error = Error; + + fn try_from(mut v: Vec) -> Result { + let len = v.len(); + // into_boxed_slice calls shrink_to_fit, which may move the pointer. + // But sometimes the contents of the Vec are already sensitive and + // mustn't be copied. So ensure the shrink_to_fit call is a NOP. + v.resize(v.capacity(), 0); + let b = v.into_boxed_slice(); + if !b.is_empty() { + // SAFETY: The address range is part of our address space. + unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?; + } + Ok(Self { elems: b, len }) + } +} diff --git a/libs/rust/build.rs b/libs/rust/build.rs index 5ad8f91..09440d8 100644 --- a/libs/rust/build.rs +++ b/libs/rust/build.rs @@ -1,18 +1,19 @@ use std::{fs, path::PathBuf, vec}; - + fn main() { + println!("cargo:rerun-if-changed=aidl"); + println!("cargo:rerun-if-changed=src/proto"); + println!("cargo:rerun-if-changed=build.rs"); + let out_dir = "src/proto"; - + fs::create_dir_all(out_dir).unwrap(); - + prost_build::Config::new() .out_dir(out_dir) - .compile_protos( - &["proto/storage.proto"], - &["proto/"], - ) + .compile_protos(&["proto/storage.proto"], &["proto/"]) .expect("Failed to compile .proto files"); - + let mod_file = format!("{}/mod.rs", out_dir); let mod_content = ["storage.rs"] .iter() @@ -43,8 +44,17 @@ fn main() { } } } - - aidl.generate().unwrap(); + aidl.source(PathBuf::from( + "aidl/android/content/pm/IPackageManager.aidl", + )) + .source(PathBuf::from( + "aidl/android/security/authorization/ResponseCode.aidl", + )) + .source(PathBuf::from( + "aidl/android/hardware/security/secureclock/ISecureClock.aidl", + )) + .generate() + .unwrap(); let generated_path = PathBuf::from(format!("{}/aidl.rs", std::env::var("OUT_DIR").unwrap())); let content = fs::read_to_string(&generated_path).unwrap(); @@ -56,6 +66,6 @@ fn main() { .replace("r#operation: rsbinder::Strong,", "r#operation: Option>,"); println!("Patched AIDL content:\n{}", generated_path.display()); - - fs::write(generated_path, patched_content).unwrap(); -} \ No newline at end of file + + fs::write(generated_path, patched_content).unwrap(); +} diff --git a/libs/rust/common/src/bin/keyblob-cddl-dump.rs b/libs/rust/common/src/bin/keyblob-cddl-dump.rs index 512b16f..b0300c2 100644 --- a/libs/rust/common/src/bin/keyblob-cddl-dump.rs +++ b/libs/rust/common/src/bin/keyblob-cddl-dump.rs @@ -49,7 +49,10 @@ impl AccumulatedSchema { #[cfg(feature = "cddl-cat")] for (name, data) in &self.samples { if let Err(e) = cddl_cat::validate_cbor_bytes(&name, &self.schema, &data) { - eprintln!("Failed to validate sample data for {} against CDDL: {:?}", name, e); + eprintln!( + "Failed to validate sample data for {} against CDDL: {:?}", + name, e + ); } } } @@ -71,7 +74,9 @@ fn main() { kek_context: vec![], encrypted_key_material: coset::CoseEncrypt0Builder::new() .protected( - coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build(), + coset::HeaderBuilder::new() + .algorithm(coset::iana::Algorithm::A256GCM) + .build(), ) .ciphertext(vec![1, 2, 3]) .build(), @@ -84,7 +89,9 @@ fn main() { kek_context: vec![], encrypted_key_material: coset::CoseEncrypt0Builder::new() .protected( - coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build(), + coset::HeaderBuilder::new() + .algorithm(coset::iana::Algorithm::A256GCM) + .build(), ) .ciphertext(vec![1, 2, 3]) .build(), @@ -100,7 +107,9 @@ fn main() { "[ protected: bstr, unprotected: { * (int / tstr) => any }, ciphertext: bstr / nil ]", ); - schema.add(crypto::KeyMaterial::Aes(crypto::aes::Key::Aes128([0u8; 16]).into())); + schema.add(crypto::KeyMaterial::Aes( + crypto::aes::Key::Aes128([0u8; 16]).into(), + )); schema.add(keyblob::SecureDeletionSlot(1)); schema.add(keyblob::SecureDeletionData { factory_reset_secret: [0; 32], @@ -129,7 +138,9 @@ fn main() { schema.add(keymint::HardwareAuthenticatorType::Fingerprint); schema.add(keymint::PaddingMode::None); - schema.add(keymint::DateTime { ms_since_epoch: 22_593_600_000 }); + schema.add(keymint::DateTime { + ms_since_epoch: 22_593_600_000, + }); schema.add(kmr_wire::KeySizeInBits(256)); schema.add(kmr_wire::RsaExponent(65537)); diff --git a/libs/rust/common/src/crypto.rs b/libs/rust/common/src/crypto.rs index 62e40f0..efd0841 100644 --- a/libs/rust/common/src/crypto.rs +++ b/libs/rust/common/src/crypto.rs @@ -39,6 +39,23 @@ pub use traits::*; /// Size of SHA-256 output in bytes. pub const SHA256_DIGEST_LEN: usize = 32; +/// Length of the expected initialization vector. +pub const GCM_IV_LENGTH: usize = 12; +/// Length of the expected AEAD TAG. +pub const TAG_LENGTH: usize = 16; +/// Length of an AES 256 key in bytes. +pub const AES_256_KEY_LENGTH: usize = 32; +/// Length of an AES 128 key in bytes. +pub const AES_128_KEY_LENGTH: usize = 16; +/// Length of the expected salt for key from password generation. +pub const SALT_LENGTH: usize = 16; +/// Length of an HMAC-SHA256 tag in bytes. +pub const HMAC_SHA256_LEN: usize = 32; + +/// Older versions of keystore produced IVs with four extra +/// ignored zero bytes at the end; recognise and trim those. +pub const LEGACY_IV_LENGTH: usize = 16; + /// Function that mimics `slice.to_vec()` but which detects allocation failures. This version emits /// `CborError` (instead of the `Error` that `crate::try_to_vec` emits). #[inline] @@ -55,7 +72,9 @@ pub struct MillisecondsSinceEpoch(pub i64); impl From for kmr_wire::secureclock::Timestamp { fn from(value: MillisecondsSinceEpoch) -> Self { - kmr_wire::secureclock::Timestamp { milliseconds: value.0 } + kmr_wire::secureclock::Timestamp { + milliseconds: value.0, + } } } @@ -291,7 +310,11 @@ impl AsCborValue for KeyMaterial { let curve_type = CurveType::from_cbor_value(a.remove(1))?; let curve = ::from_cbor_value(a.remove(0))?; if opaque { - Ok(Self::Ec(curve, curve_type, OpaqueKeyMaterial(raw_key).into())) + Ok(Self::Ec( + curve, + curve_type, + OpaqueKeyMaterial(raw_key).into(), + )) } else { let key = match (curve, curve_type) { (EcCurve::P224, CurveType::Nist) => ec::Key::P224(ec::NistKey(raw_key)), diff --git a/libs/rust/common/src/crypto/aes.rs b/libs/rust/common/src/crypto/aes.rs index 1f22c4b..3f47f41 100644 --- a/libs/rust/common/src/crypto/aes.rs +++ b/libs/rust/common/src/crypto/aes.rs @@ -67,7 +67,11 @@ impl Key { 16 => Ok(Key::Aes128(data.try_into().unwrap())), // safe: len checked 24 => Ok(Key::Aes192(data.try_into().unwrap())), // safe: len checked 32 => Ok(Key::Aes256(data.try_into().unwrap())), // safe: len checked - l => Err(km_err!(UnsupportedKeySize, "AES keys must be 16, 24 or 32 bytes not {}", l)), + l => Err(km_err!( + UnsupportedKeySize, + "AES keys must be 16, 24 or 32 bytes not {}", + l + )), } } /// Create a new [`Key`] from raw data, which must be 16, 24 or 32 bytes long. @@ -143,7 +147,10 @@ impl Mode { match mode { BlockMode::Ecb => { if caller_nonce.is_some() { - return Err(km_err!(InvalidNonce, "nonce unexpectedly provided for AES-ECB")); + return Err(km_err!( + InvalidNonce, + "nonce unexpectedly provided for AES-ECB" + )); } match padding { PaddingMode::None => Ok(Mode::Cipher(CipherMode::EcbNoPadding)), @@ -155,8 +162,9 @@ impl Mode { } } BlockMode::Cbc => { - let nonce: [u8; BLOCK_SIZE] = - nonce(BLOCK_SIZE, caller_nonce, rng)?.try_into().map_err(|_e| { + let nonce: [u8; BLOCK_SIZE] = nonce(BLOCK_SIZE, caller_nonce, rng)? + .try_into() + .map_err(|_e| { km_err!(InvalidNonce, "want {} byte nonce for AES-CBC", BLOCK_SIZE) })?; match padding { @@ -175,8 +183,9 @@ impl Mode { "expected NONE padding for AES-CTR" )); } - let nonce: [u8; BLOCK_SIZE] = - nonce(BLOCK_SIZE, caller_nonce, rng)?.try_into().map_err(|_e| { + let nonce: [u8; BLOCK_SIZE] = nonce(BLOCK_SIZE, caller_nonce, rng)? + .try_into() + .map_err(|_e| { km_err!(InvalidNonce, "want {} byte nonce for AES-CTR", BLOCK_SIZE) })?; Ok(Mode::Cipher(CipherMode::Ctr { nonce })) diff --git a/libs/rust/common/src/crypto/des.rs b/libs/rust/common/src/crypto/des.rs index e32cbd9..18c3e90 100644 --- a/libs/rust/common/src/crypto/des.rs +++ b/libs/rust/common/src/crypto/des.rs @@ -42,16 +42,16 @@ pub struct Key(pub [u8; KEY_SIZE_BYTES]); impl Key { /// Create a new 3-DES key from 24 bytes of data. pub fn new(data: Vec) -> Result { - Ok(Key(data - .try_into() - .map_err(|_e| km_err!(UnsupportedKeySize, "3-DES key size wrong"))?)) + Ok(Key(data.try_into().map_err(|_e| { + km_err!(UnsupportedKeySize, "3-DES key size wrong") + })?)) } /// Create a new 3-DES key from 24 bytes of data. pub fn new_from(data: &[u8]) -> Result { let data = try_to_vec(data)?; - Ok(Key(data - .try_into() - .map_err(|_e| km_err!(UnsupportedKeySize, "3-DES key size wrong"))?)) + Ok(Key(data.try_into().map_err(|_e| { + km_err!(UnsupportedKeySize, "3-DES key size wrong") + })?)) } } diff --git a/libs/rust/common/src/crypto/ec.rs b/libs/rust/common/src/crypto/ec.rs index 97b2998..f6d1bcb 100644 --- a/libs/rust/common/src/crypto/ec.rs +++ b/libs/rust/common/src/crypto/ec.rs @@ -345,7 +345,10 @@ pub fn coordinates_from_pub_key( curve )); } - Ok((try_to_vec(&pub_key[1..1 + coord_len])?, try_to_vec(&pub_key[1 + coord_len..])?)) + Ok(( + try_to_vec(&pub_key[1..1 + coord_len])?, + try_to_vec(&pub_key[1 + coord_len..])?, + )) } /// An Ed25519 private key. @@ -380,7 +383,10 @@ pub fn import_sec1_private_key(data: &[u8]) -> Result { let ec_key = sec1::EcPrivateKey::from_der(data) .map_err(|e| der_err!(e, "failed to parse ECPrivateKey"))?; let ec_parameters = ec_key.parameters.ok_or_else(|| { - km_err!(InvalidArgument, "sec1 formatted EC private key didn't have a parameters field") + km_err!( + InvalidArgument, + "sec1 formatted EC private key didn't have a parameters field" + ) })?; let parameters_oid = ec_parameters.named_curve().ok_or_else(|| { km_err!( @@ -388,8 +394,10 @@ pub fn import_sec1_private_key(data: &[u8]) -> Result { "couldn't retrieve parameters oid from sec1 ECPrivateKey formatted ec key parameters" ) })?; - let algorithm = - AlgorithmIdentifier { oid: X509_NIST_OID, parameters: Some(AnyRef::from(¶meters_oid)) }; + let algorithm = AlgorithmIdentifier { + oid: X509_NIST_OID, + parameters: Some(AnyRef::from(¶meters_oid)), + }; let pkcs8_key = pkcs8::PrivateKeyInfo::new(algorithm, data); import_pkcs8_key_impl(&pkcs8_key) } @@ -417,18 +425,22 @@ fn import_pkcs8_key_impl(key_info: &pkcs8::PrivateKeyInfo) -> Result { - (EcCurve::P224, Key::P224(NistKey(try_to_vec(key_info.private_key)?))) - } - ALGO_PARAM_P256_OID => { - (EcCurve::P256, Key::P256(NistKey(try_to_vec(key_info.private_key)?))) - } - ALGO_PARAM_P384_OID => { - (EcCurve::P384, Key::P384(NistKey(try_to_vec(key_info.private_key)?))) - } - ALGO_PARAM_P521_OID => { - (EcCurve::P521, Key::P521(NistKey(try_to_vec(key_info.private_key)?))) - } + ALGO_PARAM_P224_OID => ( + EcCurve::P224, + Key::P224(NistKey(try_to_vec(key_info.private_key)?)), + ), + ALGO_PARAM_P256_OID => ( + EcCurve::P256, + Key::P256(NistKey(try_to_vec(key_info.private_key)?)), + ), + ALGO_PARAM_P384_OID => ( + EcCurve::P384, + Key::P384(NistKey(try_to_vec(key_info.private_key)?)), + ), + ALGO_PARAM_P521_OID => ( + EcCurve::P521, + Key::P521(NistKey(try_to_vec(key_info.private_key)?)), + ), oid => { return Err(km_err!( ImportParameterMismatch, @@ -441,7 +453,10 @@ fn import_pkcs8_key_impl(key_info: &pkcs8::PrivateKeyInfo) -> Result { if algo_params.is_some() { - Err(km_err!(InvalidArgument, "unexpected PKCS#8 parameters for Ed25519 import")) + Err(km_err!( + InvalidArgument, + "unexpected PKCS#8 parameters for Ed25519 import" + )) } else { // For Ed25519 the PKCS#8 `privateKey` field holds a `CurvePrivateKey` // (RFC 8410 s7) that is an OCTET STRING holding the raw key. As this is DER, @@ -450,14 +465,20 @@ fn import_pkcs8_key_impl(key_info: &pkcs8::PrivateKeyInfo) -> Result { if algo_params.is_some() { - Err(km_err!(InvalidArgument, "unexpected PKCS#8 parameters for X25519 import",)) + Err(km_err!( + InvalidArgument, + "unexpected PKCS#8 parameters for X25519 import", + )) } else { // For X25519 the PKCS#8 `privateKey` field holds a `CurvePrivateKey` // (RFC 8410 s7) that is an OCTET STRING holding the raw key. As this is DER, @@ -466,7 +487,10 @@ fn import_pkcs8_key_impl(key_info: &pkcs8::PrivateKeyInfo) -> Result Result Result { let key = data.try_into().map_err(|_e| { - km_err!(InvalidInputLength, "import Ed25519 key of incorrect len {}", data.len()) + km_err!( + InvalidInputLength, + "import Ed25519 key of incorrect len {}", + data.len() + ) })?; - Ok(KeyMaterial::Ec(EcCurve::Curve25519, CurveType::EdDsa, Key::Ed25519(Ed25519Key(key)).into())) + Ok(KeyMaterial::Ec( + EcCurve::Curve25519, + CurveType::EdDsa, + Key::Ed25519(Ed25519Key(key)).into(), + )) } /// Import a 32-byte raw X25519 key. pub fn import_raw_x25519_key(data: &[u8]) -> Result { let key = data.try_into().map_err(|_e| { - km_err!(InvalidInputLength, "import X25519 key of incorrect len {}", data.len()) + km_err!( + InvalidInputLength, + "import X25519 key of incorrect len {}", + data.len() + ) })?; - Ok(KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, Key::X25519(X25519Key(key)).into())) + Ok(KeyMaterial::Ec( + EcCurve::Curve25519, + CurveType::Xdh, + Key::X25519(X25519Key(key)).into(), + )) } /// Convert a signature as emitted from the `Ec` trait into the form needed for @@ -546,9 +586,13 @@ pub fn from_cose_signature(curve: EcCurve, sig: &[u8]) -> Result, Error> s: der::asn1::UintRef::new(&sig[l..]) .map_err(|e| km_err!(EncodingError, "failed to build INTEGER: {:?}", e))?, }; - der_sig - .to_der() - .map_err(|e| km_err!(EncodingError, "failed to encode signature SEQUENCE: {:?}", e)) + der_sig.to_der().map_err(|e| { + km_err!( + EncodingError, + "failed to encode signature SEQUENCE: {:?}", + e + ) + }) } EcCurve::Curve25519 => { // Ed25519 signatures can be used as-is (RFC 8410 section 6) diff --git a/libs/rust/common/src/crypto/hmac.rs b/libs/rust/common/src/crypto/hmac.rs index 745c066..699f5a9 100644 --- a/libs/rust/common/src/crypto/hmac.rs +++ b/libs/rust/common/src/crypto/hmac.rs @@ -33,9 +33,17 @@ pub struct Key(pub Vec); fn valid_size(key_size: KeySizeInBits, max_size_bits: usize) -> Result<(), Error> { if key_size.0 % 8 != 0 { - Err(km_err!(UnsupportedKeySize, "key size {} bits not a multiple of 8", key_size.0)) + Err(km_err!( + UnsupportedKeySize, + "key size {} bits not a multiple of 8", + key_size.0 + )) } else if !(MIN_KEY_SIZE_BITS..=max_size_bits).contains(&(key_size.0 as usize)) { - Err(km_err!(UnsupportedKeySize, "unsupported KEY_SIZE {} bits for HMAC", key_size.0)) + Err(km_err!( + UnsupportedKeySize, + "unsupported KEY_SIZE {} bits for HMAC", + key_size.0 + )) } else { Ok(()) } diff --git a/libs/rust/common/src/crypto/rsa.rs b/libs/rust/common/src/crypto/rsa.rs index 1ba59cb..0edea2e 100644 --- a/libs/rust/common/src/crypto/rsa.rs +++ b/libs/rust/common/src/crypto/rsa.rs @@ -128,7 +128,10 @@ impl OpaqueOr { let pub_key = rsa.subject_public_key(self)?; buf.try_extend_from_slice(&pub_key)?; Ok(SubjectPublicKeyInfo { - algorithm: AlgorithmIdentifier { oid: X509_OID, parameters: Some(der::AnyRef::NULL) }, + algorithm: AlgorithmIdentifier { + oid: X509_OID, + parameters: Some(der::AnyRef::NULL), + }, subject_public_key: BitStringRef::from_bytes(buf) .map_err(|e| km_err!(UnknownError, "invalid bitstring: {e:?}"))?, }) @@ -160,7 +163,10 @@ impl DecryptionMode { PaddingMode::RsaOaep => { let msg_digest = tag::get_digest(params)?; let mgf_digest = tag::get_mgf_digest(params)?; - Ok(DecryptionMode::OaepPadding { msg_digest, mgf_digest }) + Ok(DecryptionMode::OaepPadding { + msg_digest, + mgf_digest, + }) } PaddingMode::RsaPkcs115Encrypt => Ok(DecryptionMode::Pkcs1_1_5Padding), _ => Err(km_err!( @@ -245,5 +251,9 @@ pub fn import_pkcs1_key( pub_exponent_arr[offset..].copy_from_slice(pub_exponent_bytes); let pub_exponent = u64::from_be_bytes(pub_exponent_arr); - Ok((KeyMaterial::Rsa(key.into()), KeySizeInBits(key_size), RsaExponent(pub_exponent))) + Ok(( + KeyMaterial::Rsa(key.into()), + KeySizeInBits(key_size), + RsaExponent(pub_exponent), + )) } diff --git a/libs/rust/common/src/crypto/traits.rs b/libs/rust/common/src/crypto/traits.rs index 5c9d0eb..c5c8f8f 100644 --- a/libs/rust/common/src/crypto/traits.rs +++ b/libs/rust/common/src/crypto/traits.rs @@ -518,7 +518,11 @@ pub trait Sha256: Send { #[macro_export] macro_rules! log_unimpl { () => { - error!("{}:{}: Unimplemented placeholder KeyMint trait method invoked!", file!(), line!(),); + error!( + "{}:{}: Unimplemented placeholder KeyMint trait method invoked!", + file!(), + line!(), + ); }; } diff --git a/libs/rust/common/src/keyblob.rs b/libs/rust/common/src/keyblob.rs index 4dd07a7..3b77642 100644 --- a/libs/rust/common/src/keyblob.rs +++ b/libs/rust/common/src/keyblob.rs @@ -243,7 +243,10 @@ impl Drop for SlotHolder<'_> { fn drop(&mut self) { if let Some(slot) = self.slot.take() { if let Err(e) = self.mgr.delete_secret(slot) { - error!("Failed to delete recently-acquired SDD slot {:?}: {:?}", slot, e); + error!( + "Failed to delete recently-acquired SDD slot {:?}: {:?}", + slot, e + ); } } } @@ -257,7 +260,13 @@ impl<'a> SlotHolder<'a> { purpose: SlotPurpose, ) -> Result<(Self, SecureDeletionData), Error> { let (slot, sdd) = mgr.new_secret(rng, purpose)?; - Ok((Self { mgr, slot: Some(slot) }, sdd)) + Ok(( + Self { + mgr, + slot: Some(slot), + }, + sdd, + )) } /// Acquire ownership of the secure deletion slot. @@ -331,7 +340,11 @@ impl PlaintextKeyBlob { if contains_tag_value!(self.characteristics_at(sec_level)?, Purpose, purpose) { Ok(()) } else { - Err(km_err!(IncompatiblePurpose, "purpose {:?} not supported by keyblob", purpose)) + Err(km_err!( + IncompatiblePurpose, + "purpose {:?} not supported by keyblob", + purpose + )) } } } @@ -356,7 +369,12 @@ pub fn encrypt( let requires_sdd = plaintext_keyblob .characteristics_at(sec_level)? .iter() - .any(|param| matches!(param, KeyParam::RollbackResistance | KeyParam::UsageCountLimit(1))); + .any(|param| { + matches!( + param, + KeyParam::RollbackResistance | KeyParam::UsageCountLimit(1) + ) + }); let (slot_holder, sdd) = match (requires_sdd, sdd_mgr) { (true, Some(sdd_mgr)) => { // Reserve a slot and store it in a [`SlotHolder`] so that it will definitely be @@ -383,12 +401,22 @@ pub fn encrypt( let characteristics = plaintext_keyblob.characteristics; let mut key_derivation_input = [0u8; 32]; rng.fill_bytes(&mut key_derivation_input[..]); - let kek = - derive_kek(kdf, root_key, &key_derivation_input, characteristics.clone(), hidden, sdd)?; + let kek = derive_kek( + kdf, + root_key, + &key_derivation_input, + characteristics.clone(), + hidden, + sdd, + )?; // Encrypt the plaintext key material into a `Cose_Encrypt0` structure. let cose_encrypt = coset::CoseEncrypt0Builder::new() - .protected(coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build()) + .protected( + coset::HeaderBuilder::new() + .algorithm(coset::iana::Algorithm::A256GCM) + .build(), + ) .try_create_ciphertext::<_, Error>( &plaintext_keyblob.key_material.into_vec()?, &[], @@ -464,7 +492,8 @@ pub fn decrypt( op.update_aad(&extended_aad)?; let mut pt_data = op.update(&cose_encrypt.ciphertext.unwrap_or_default())?; pt_data.try_extend_from_slice( - &op.finish().map_err(|e| km_err!(InvalidKeyBlob, "failed to decrypt keyblob: {:?}", e))?, + &op.finish() + .map_err(|e| km_err!(InvalidKeyBlob, "failed to decrypt keyblob: {:?}", e))?, )?; Ok(PlaintextKeyBlob { diff --git a/libs/rust/common/src/keyblob/legacy.rs b/libs/rust/common/src/keyblob/legacy.rs index 1df5a36..6589b86 100644 --- a/libs/rust/common/src/keyblob/legacy.rs +++ b/libs/rust/common/src/keyblob/legacy.rs @@ -48,7 +48,10 @@ pub enum AuthEncryptedBlobFormat { impl AuthEncryptedBlobFormat { /// Indicate whether this format requires secure deletion support. pub fn requires_secure_deletion(&self) -> bool { - matches!(self, Self::AesGcmWithSecureDeletion | Self::AesGcmWithSecureDeletionVersioned) + matches!( + self, + Self::AesGcmWithSecureDeletion | Self::AesGcmWithSecureDeletionVersioned + ) } /// Indicate whether this format is versioned. pub fn is_versioned(&self) -> bool { @@ -111,10 +114,18 @@ impl EncryptedKeyBlob { result.extend_from_slice(&self.tag); if self.format.is_versioned() { let kdf_version = self.kdf_version.ok_or_else(|| { - km_err!(InvalidKeyBlob, "keyblob of format {:?} missing kdf_version", self.format) + km_err!( + InvalidKeyBlob, + "keyblob of format {:?} missing kdf_version", + self.format + ) })?; let addl_info = self.addl_info.ok_or_else(|| { - km_err!(InvalidKeyBlob, "keyblob of format {:?} missing addl_info", self.format) + km_err!( + InvalidKeyBlob, + "keyblob of format {:?} missing addl_info", + self.format + ) })? as u32; result.extend_from_slice(&kdf_version.to_ne_bytes()); result.extend_from_slice(&addl_info.to_ne_bytes()); @@ -161,7 +172,13 @@ impl EncryptedKeyBlob { let key_slot = match data.len() { 0 => None, 4 => Some(consume_u32(&mut data)?), - _ => return Err(km_err!(InvalidKeyBlob, "unexpected remaining length {}", data.len())), + _ => { + return Err(km_err!( + InvalidKeyBlob, + "unexpected remaining length {}", + data.len() + )) + } }; Ok(EncryptedKeyBlob { @@ -226,7 +243,11 @@ impl KeyBlob { comparator: E, ) -> Result { if data.len() < (Self::MAC_LEN + 4 + 4 + 4) { - return Err(km_err!(InvalidKeyBlob, "blob not long enough (len = {})", data.len())); + return Err(km_err!( + InvalidKeyBlob, + "blob not long enough (len = {})", + data.len() + )); } // Check the HMAC in the last 8 bytes before doing anything else. @@ -238,7 +259,11 @@ impl KeyBlob { let version = consume_u8(&mut data)?; if version != KEY_BLOB_VERSION { - return Err(km_err!(InvalidKeyBlob, "unexpected blob version {}", version)); + return Err(km_err!( + InvalidKeyBlob, + "unexpected blob version {}", + version + )); } let key_material = consume_vec(&mut data)?; let hw_enforced = crate::tag::legacy::deserialize(&mut data)?; @@ -249,7 +274,11 @@ impl KeyBlob { if !rest.is_empty() { return Err(km_err!(InvalidKeyBlob, "extra data (len {})", rest.len())); } - Ok(KeyBlob { key_material, hw_enforced, sw_enforced }) + Ok(KeyBlob { + key_material, + hw_enforced, + sw_enforced, + }) } /// Compute the authentication HMAC for a KeyBlob. This is built as: diff --git a/libs/rust/common/src/keyblob/tests.rs b/libs/rust/common/src/keyblob/tests.rs index 4c6b991..49871d2 100644 --- a/libs/rust/common/src/keyblob/tests.rs +++ b/libs/rust/common/src/keyblob/tests.rs @@ -69,10 +69,14 @@ fn test_sdd_factory_secret() { let mut sdd_mgr = InMemorySlotManager::<10>::default(); let mut rng = FakeRng::default(); assert!(sdd_mgr.get_factory_reset_secret().is_err()); - let secret1 = sdd_mgr.get_or_create_factory_reset_secret(&mut rng).unwrap(); + let secret1 = sdd_mgr + .get_or_create_factory_reset_secret(&mut rng) + .unwrap(); let secret2 = sdd_mgr.get_factory_reset_secret().unwrap(); assert!(secret1 == secret2); - let secret3 = sdd_mgr.get_or_create_factory_reset_secret(&mut rng).unwrap(); + let secret3 = sdd_mgr + .get_or_create_factory_reset_secret(&mut rng) + .unwrap(); assert!(secret1 == secret3); } @@ -80,11 +84,19 @@ fn test_sdd_factory_secret() { fn test_sdd_exhaustion() { let mut sdd_mgr = InMemorySlotManager::<2>::default(); let mut rng = FakeRng::default(); - let (_slot0, _sdd0) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); - let (slot1a, sdd1a) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); - assert!(sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).is_err()); + let (_slot0, _sdd0) = sdd_mgr + .new_secret(&mut rng, SlotPurpose::KeyGeneration) + .unwrap(); + let (slot1a, sdd1a) = sdd_mgr + .new_secret(&mut rng, SlotPurpose::KeyGeneration) + .unwrap(); + assert!(sdd_mgr + .new_secret(&mut rng, SlotPurpose::KeyGeneration) + .is_err()); sdd_mgr.delete_secret(slot1a).unwrap(); - let (slot1b, sdd1b) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + let (slot1b, sdd1b) = sdd_mgr + .new_secret(&mut rng, SlotPurpose::KeyGeneration) + .unwrap(); assert_eq!(slot1a, slot1b); assert!(sdd1a != sdd1b); } diff --git a/libs/rust/common/src/tag.rs b/libs/rust/common/src/tag.rs index 7b0e96e..96033ea 100644 --- a/libs/rust/common/src/tag.rs +++ b/libs/rust/common/src/tag.rs @@ -153,9 +153,11 @@ pub fn characteristics_valid(characteristics: &[KeyParam]) -> Result<(), Error> let tag = param.tag(); dup_checker.add(tag)?; if info(tag)?.characteristic == Characteristic::NotKeyCharacteristic { - return Err( - km_err!(InvalidKeyBlob, "tag {:?} is not a valid key characteristic", tag,), - ); + return Err(km_err!( + InvalidKeyBlob, + "tag {:?} is not a valid key characteristic", + tag, + )); } } Ok(()) @@ -231,11 +233,19 @@ pub fn characteristics_at( if result.is_none() { result = Some(&chars.authorizations); } else { - return Err(km_err!(InvalidKeyBlob, "multiple key characteristics at {:?}", sec_level)); + return Err(km_err!( + InvalidKeyBlob, + "multiple key characteristics at {:?}", + sec_level + )); } } result.ok_or_else(|| { - km_err!(InvalidKeyBlob, "no parameters at security level {:?} found", sec_level) + km_err!( + InvalidKeyBlob, + "no parameters at security level {:?} found", + sec_level + ) }) } @@ -278,7 +288,10 @@ pub fn extract_key_gen_characteristics( Algorithm::TripleDes => check_3des_gen_params(params), Algorithm::Hmac => check_hmac_gen_params(params, sec_level), }?; - Ok((extract_key_characteristics(secure_storage, params, &[], sec_level)?, keygen_info)) + Ok(( + extract_key_characteristics(secure_storage, params, &[], sec_level)?, + keygen_info, + )) } /// Build the set of key characteristics for a key that is about to be imported, @@ -327,14 +340,21 @@ fn extract_key_characteristics( // Input params should not contain anything that KeyMint adds itself. if AUTO_ADDED_CHARACTERISTICS.contains(&tag) { - return Err(km_err!(InvalidTag, "KeyMint-added tag included on key generation/import")); + return Err(km_err!( + InvalidTag, + "KeyMint-added tag included on key generation/import" + )); } if sec_level == SecurityLevel::Strongbox && [Tag::MaxUsesPerBoot, Tag::RollbackResistance].contains(&tag) { // StrongBox does not support tags that require per-key storage. - return Err(km_err!(InvalidTag, "tag {:?} not allowed in StrongBox", param.tag())); + return Err(km_err!( + InvalidTag, + "tag {:?} not allowed in StrongBox", + param.tag() + )); } // UsageCountLimit is peculiar. If its value is > 1, it should be Keystore-enforced. @@ -365,7 +385,10 @@ fn extract_key_characteristics( keystore_chars.sort_by(legacy::param_compare); let mut result = Vec::new(); - result.try_push(KeyCharacteristics { security_level: sec_level, authorizations: chars })?; + result.try_push(KeyCharacteristics { + security_level: sec_level, + authorizations: chars, + })?; if !keystore_chars.is_empty() { result.try_push(KeyCharacteristics { security_level: SecurityLevel::Keystore, @@ -385,7 +408,11 @@ fn check_rsa_key_size(key_size: KeySizeInBits, sec_level: SecurityLevel) -> Resu KeySizeInBits(2048) => Ok(()), KeySizeInBits(3072) if sec_level != SecurityLevel::Strongbox => Ok(()), KeySizeInBits(4096) if sec_level != SecurityLevel::Strongbox => Ok(()), - _ => Err(km_err!(UnsupportedKeySize, "unsupported KEY_SIZE {:?} bits for RSA", key_size)), + _ => Err(km_err!( + UnsupportedKeySize, + "unsupported KEY_SIZE {:?} bits for RSA", + key_size + )), } } @@ -514,9 +541,13 @@ fn check_ec_gen_params(params: &[KeyParam], sec_level: SecurityLevel) -> Result< pub fn primary_purpose(params: &[KeyParam]) -> Result { params .iter() - .find_map( - |param| if let KeyParam::Purpose(purpose) = param { Some(*purpose) } else { None }, - ) + .find_map(|param| { + if let KeyParam::Purpose(purpose) = param { + Some(*purpose) + } else { + None + } + }) .ok_or_else(|| km_err!(IncompatiblePurpose, "no purpose found for key!")) } @@ -538,9 +569,15 @@ fn check_ec_import_params( // Raw key import must specify the curve (and the only valid option is Curve25519 // currently). if primary_purpose(params)? == KeyPurpose::AgreeKey { - (ec.import_raw_x25519_key(key_data, params)?, EcCurve::Curve25519) + ( + ec.import_raw_x25519_key(key_data, params)?, + EcCurve::Curve25519, + ) } else { - (ec.import_raw_ed25519_key(key_data, params)?, EcCurve::Curve25519) + ( + ec.import_raw_ed25519_key(key_data, params)?, + EcCurve::Curve25519, + ) } } KeyFormat::Pkcs8 => { @@ -558,7 +595,10 @@ fn check_ec_import_params( } KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, _) => { if primary_purpose(params)? != KeyPurpose::AgreeKey { - return Err(km_err!(IncompatiblePurpose, "can't use XDH key for signing")); + return Err(km_err!( + IncompatiblePurpose, + "can't use XDH key for signing" + )); } EcCurve::Curve25519 } @@ -625,7 +665,11 @@ fn check_ec_params( sec_level: SecurityLevel, ) -> Result, Error> { if sec_level == SecurityLevel::Strongbox && curve != EcCurve::P256 { - return Err(km_err!(UnsupportedEcCurve, "invalid curve ({:?}) for StrongBox", curve)); + return Err(km_err!( + UnsupportedEcCurve, + "invalid curve ({:?}) for StrongBox", + curve + )); } // Key size is not needed, but if present should match the curve. @@ -737,7 +781,9 @@ fn check_aes_import_params( /// Check the parameter validity for an AES key that is about to be generated or imported. fn check_aes_params(params: &[KeyParam]) -> Result<(), Error> { - let gcm_support = params.iter().any(|p| *p == KeyParam::BlockMode(BlockMode::Gcm)); + let gcm_support = params + .iter() + .any(|p| *p == KeyParam::BlockMode(BlockMode::Gcm)); if gcm_support { let min_mac_len = get_tag_value!(params, MinMacLength, ErrorCode::MissingMinMacLength)?; if (min_mac_len % 8 != 0) || !(96..=128).contains(&min_mac_len) { @@ -824,7 +870,11 @@ fn check_hmac_params( } let digest = get_tag_value!(params, Digest, ErrorCode::UnsupportedDigest)?; if digest == Digest::None { - return Err(km_err!(UnsupportedDigest, "unsupported digest {:?}", digest)); + return Err(km_err!( + UnsupportedDigest, + "unsupported digest {:?}", + digest + )); } let min_mac_len = get_tag_value!(params, MinMacLength, ErrorCode::MissingMinMacLength)?; @@ -919,14 +969,23 @@ fn reject_incompatible_auth(params: &[KeyParam]) -> Result<(), Error> { if seen_no_auth { if seen_user_secure_id { - return Err(km_err!(InvalidTag, "found both NO_AUTH_REQUIRED and USER_SECURE_ID")); + return Err(km_err!( + InvalidTag, + "found both NO_AUTH_REQUIRED and USER_SECURE_ID" + )); } if seen_auth_type { - return Err(km_err!(InvalidTag, "found both NO_AUTH_REQUIRED and USER_AUTH_TYPE")); + return Err(km_err!( + InvalidTag, + "found both NO_AUTH_REQUIRED and USER_AUTH_TYPE" + )); } } if seen_user_secure_id && !seen_auth_type { - return Err(km_err!(InvalidTag, "found USER_SECURE_ID but no USER_AUTH_TYPE")); + return Err(km_err!( + InvalidTag, + "found USER_SECURE_ID but no USER_AUTH_TYPE" + )); } Ok(()) } @@ -985,7 +1044,10 @@ pub fn check_begin_params( if get_bool_tag_value!(chars, CallerNonce)? { // Caller-provided nonces are allowed. } else if nonce.is_some() && purpose == KeyPurpose::Encrypt { - return Err(km_err!(CallerNonceProhibited, "caller nonce not allowed for encryption")); + return Err(km_err!( + CallerNonceProhibited, + "caller nonce not allowed for encryption" + )); } // Further algorithm-specific checks. @@ -1039,8 +1101,9 @@ pub fn check_begin_params( let mut mgf_digest_to_find = get_opt_tag_value!(params, RsaOaepMgfDigest, UnsupportedMgfDigest)?; - let chars_have_mgf_digest = - chars.iter().any(|param| matches!(param, KeyParam::RsaOaepMgfDigest(_))); + let chars_have_mgf_digest = chars + .iter() + .any(|param| matches!(param, KeyParam::RsaOaepMgfDigest(_))); if chars_have_mgf_digest && mgf_digest_to_find.is_none() { // The key characteristics include an explicit set of MGF digests, but the begin() // operation is using the default SHA1. Check that this default is in the @@ -1132,7 +1195,10 @@ fn check_begin_rsa_params( } PaddingMode::RsaPkcs115Sign if for_signing(purpose) => { if digest.is_none() { - return Err(km_err!(IncompatibleDigest, "digest required for RSA-PKCS_1_5_SIGN")); + return Err(km_err!( + IncompatibleDigest, + "digest required for RSA-PKCS_1_5_SIGN" + )); } } _ => { @@ -1145,7 +1211,12 @@ fn check_begin_rsa_params( } } - Ok(BeginParamsToCheck { block_mode: false, padding: true, digest: true, mgf_digest: true }) + Ok(BeginParamsToCheck { + block_mode: false, + padding: true, + digest: true, + mgf_digest: true, + }) } /// Check that an EC operation with the given `purpose` and `params` can validly be started @@ -1159,7 +1230,10 @@ fn check_begin_ec_params( if purpose == KeyPurpose::Sign { let digest = get_digest(params)?; if digest == Digest::Md5 { - return Err(km_err!(UnsupportedDigest, "Digest::MD5 unsupported for EC signing")); + return Err(km_err!( + UnsupportedDigest, + "Digest::MD5 unsupported for EC signing" + )); } if curve == EcCurve::Curve25519 && digest != Digest::None { return Err(km_err!( @@ -1169,7 +1243,12 @@ fn check_begin_ec_params( )); } } - Ok(BeginParamsToCheck { block_mode: false, padding: false, digest: true, mgf_digest: false }) + Ok(BeginParamsToCheck { + block_mode: false, + padding: false, + digest: true, + mgf_digest: false, + }) } /// Check that an AES operation with the given `purpose` and `params` can validly be started @@ -1237,7 +1316,12 @@ fn check_begin_aes_params( } } } - Ok(BeginParamsToCheck { block_mode: true, padding: true, digest: false, mgf_digest: false }) + Ok(BeginParamsToCheck { + block_mode: true, + padding: true, + digest: false, + mgf_digest: false, + }) } /// Check that a 3-DES operation with the given `purpose` and `params` can validly be started @@ -1254,7 +1338,11 @@ fn check_begin_3des_params( match bmode { BlockMode::Cbc | BlockMode::Ecb => {} _ => { - return Err(km_err!(UnsupportedBlockMode, "block mode {:?} not valid for 3-DES", bmode)) + return Err(km_err!( + UnsupportedBlockMode, + "block mode {:?} not valid for 3-DES", + bmode + )) } } @@ -1271,7 +1359,12 @@ fn check_begin_3des_params( } } } - Ok(BeginParamsToCheck { block_mode: true, padding: true, digest: false, mgf_digest: false }) + Ok(BeginParamsToCheck { + block_mode: true, + padding: true, + digest: false, + mgf_digest: false, + }) } /// Check that an HMAC operation with the given `purpose` and `params` can validly be started @@ -1300,7 +1393,12 @@ fn check_begin_hmac_params( } } - Ok(BeginParamsToCheck { block_mode: false, padding: false, digest: true, mgf_digest: false }) + Ok(BeginParamsToCheck { + block_mode: false, + padding: false, + digest: true, + mgf_digest: false, + }) } /// Return the length in bits of a [`Digest`] function. @@ -1324,7 +1422,10 @@ pub fn check_rsa_wrapping_key_params( ) -> Result { // Check the purpose of the wrapping key if !contains_tag_value!(chars, Purpose, KeyPurpose::WrapKey) { - return Err(km_err!(IncompatiblePurpose, "no wrap key purpose for the wrapping key")); + return Err(km_err!( + IncompatiblePurpose, + "no wrap key purpose for the wrapping key" + )); } let padding_mode = get_tag_value!(params, Padding, ErrorCode::IncompatiblePaddingMode)?; if padding_mode != PaddingMode::RsaOaep { @@ -1344,7 +1445,10 @@ pub fn check_rsa_wrapping_key_params( } let opt_mgf_digest = get_opt_tag_value!(params, RsaOaepMgfDigest)?; if opt_mgf_digest == Some(&Digest::None) { - return Err(km_err!(UnsupportedMgfDigest, "MGF digest cannot be NONE for RSA-OAEP")); + return Err(km_err!( + UnsupportedMgfDigest, + "MGF digest cannot be NONE for RSA-OAEP" + )); } if !contains_tag_value!(chars, Padding, padding_mode) { @@ -1377,7 +1481,10 @@ pub fn check_rsa_wrapping_key_params( } let mgf_digest = opt_mgf_digest.unwrap_or(&Digest::Sha1); - let rsa_oaep_decrypt_mode = DecryptionMode::OaepPadding { msg_digest, mgf_digest: *mgf_digest }; + let rsa_oaep_decrypt_mode = DecryptionMode::OaepPadding { + msg_digest, + mgf_digest: *mgf_digest, + }; Ok(rsa_oaep_decrypt_mode) } diff --git a/libs/rust/common/src/tag/info.rs b/libs/rust/common/src/tag/info.rs index ad5405d..abd26a1 100644 --- a/libs/rust/common/src/tag/info.rs +++ b/libs/rust/common/src/tag/info.rs @@ -89,8 +89,13 @@ pub const KEYMINT_ENFORCED_CHARACTERISTICS: &[Tag] = &[ ]; /// The set of characteristics that are automatically added by KeyMint on key generation. -pub const AUTO_ADDED_CHARACTERISTICS: &[Tag] = - &[Tag::Origin, Tag::OsVersion, Tag::OsPatchlevel, Tag::VendorPatchlevel, Tag::BootPatchlevel]; +pub const AUTO_ADDED_CHARACTERISTICS: &[Tag] = &[ + Tag::Origin, + Tag::OsVersion, + Tag::OsPatchlevel, + Tag::VendorPatchlevel, + Tag::BootPatchlevel, +]; /// Indicate the allowed use of the tag as a parameter for an operation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/libs/rust/common/src/tag/info/tests.rs b/libs/rust/common/src/tag/info/tests.rs index dd0d07e..7b2c01a 100644 --- a/libs/rust/common/src/tag/info/tests.rs +++ b/libs/rust/common/src/tag/info/tests.rs @@ -25,7 +25,10 @@ fn test_auto_added_const() { } } let got: BTreeSet = AUTO_ADDED_CHARACTERISTICS.iter().cloned().collect(); - assert_eq!(want, got, "AUTO_ADDED_CHARACTERISTICS constant doesn't match INFO contents"); + assert_eq!( + want, got, + "AUTO_ADDED_CHARACTERISTICS constant doesn't match INFO contents" + ); } #[test] @@ -37,7 +40,10 @@ fn test_keystore_enforced_const() { } } let got: BTreeSet = KEYSTORE_ENFORCED_CHARACTERISTICS.iter().cloned().collect(); - assert_eq!(want, got, "KEYSTORE_ENFORCED_CHARACTERISTICS constant doesn't match INFO contents"); + assert_eq!( + want, got, + "KEYSTORE_ENFORCED_CHARACTERISTICS constant doesn't match INFO contents" + ); } #[test] @@ -49,7 +55,10 @@ fn test_keymint_enforced_const() { } } let got: BTreeSet = KEYMINT_ENFORCED_CHARACTERISTICS.iter().cloned().collect(); - assert_eq!(want, got, "KEYMINT_ENFORCED_CHARACTERISTICS constant doesn't match INFO contents"); + assert_eq!( + want, got, + "KEYMINT_ENFORCED_CHARACTERISTICS constant doesn't match INFO contents" + ); } #[test] @@ -75,7 +84,10 @@ fn test_tag_tracker() { (true, vec![Tag::BlockMode, Tag::Padding, Tag::BlockMode]), (true, vec![Tag::BlockMode, Tag::EcCurve]), (true, vec![Tag::BlockMode, Tag::EcCurve, Tag::BlockMode]), - (false, vec![Tag::BlockMode, Tag::EcCurve, Tag::BlockMode, Tag::EcCurve]), + ( + false, + vec![Tag::BlockMode, Tag::EcCurve, Tag::BlockMode, Tag::EcCurve], + ), ]; for (valid, list) in tests { let mut tracker = DuplicateTagChecker::default(); diff --git a/libs/rust/common/src/tag/legacy.rs b/libs/rust/common/src/tag/legacy.rs index c0c484e..da4dd9d 100644 --- a/libs/rust/common/src/tag/legacy.rs +++ b/libs/rust/common/src/tag/legacy.rs @@ -44,7 +44,10 @@ pub fn consume_bool(data: &mut &[u8]) -> Result<(), Error> { if b == 0x01 { Ok(()) } else { - Err(km_err!(InvalidKeyBlob, "bool value other than 1 encountered")) + Err(km_err!( + InvalidKeyBlob, + "bool value other than 1 encountered" + )) } } @@ -424,24 +427,24 @@ pub fn deserialize(data: &mut &[u8]) -> Result, Error> { } // `DateTime`-holding variants. - Tag::ActiveDatetime => { - KeyParam::ActiveDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) - } - Tag::OriginationExpireDatetime => { - KeyParam::OriginationExpireDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) - } - Tag::UsageExpireDatetime => { - KeyParam::UsageExpireDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) - } - Tag::CreationDatetime => { - KeyParam::CreationDatetime(DateTime { ms_since_epoch: consume_i64(data)? }) - } - Tag::CertificateNotBefore => { - KeyParam::CertificateNotBefore(DateTime { ms_since_epoch: consume_i64(data)? }) - } - Tag::CertificateNotAfter => { - KeyParam::CertificateNotAfter(DateTime { ms_since_epoch: consume_i64(data)? }) - } + Tag::ActiveDatetime => KeyParam::ActiveDatetime(DateTime { + ms_since_epoch: consume_i64(data)?, + }), + Tag::OriginationExpireDatetime => KeyParam::OriginationExpireDatetime(DateTime { + ms_since_epoch: consume_i64(data)?, + }), + Tag::UsageExpireDatetime => KeyParam::UsageExpireDatetime(DateTime { + ms_since_epoch: consume_i64(data)?, + }), + Tag::CreationDatetime => KeyParam::CreationDatetime(DateTime { + ms_since_epoch: consume_i64(data)?, + }), + Tag::CertificateNotBefore => KeyParam::CertificateNotBefore(DateTime { + ms_since_epoch: consume_i64(data)?, + }), + Tag::CertificateNotAfter => KeyParam::CertificateNotAfter(DateTime { + ms_since_epoch: consume_i64(data)?, + }), // `Vec`-holding variants. Tag::ApplicationId => { diff --git a/libs/rust/common/src/tag/tests.rs b/libs/rust/common/src/tag/tests.rs index 9b215c7..7dc2c31 100644 --- a/libs/rust/common/src/tag/tests.rs +++ b/libs/rust/common/src/tag/tests.rs @@ -19,8 +19,14 @@ use kmr_wire::{keymint::KeyParam, KeySizeInBits}; #[test] fn test_characteristics_invalid() { let tests = vec![ - (vec![KeyParam::UsageCountLimit(42), KeyParam::UsageCountLimit(43)], "duplicate value"), - (vec![KeyParam::Nonce(vec![1, 2])], "not a valid key characteristic"), + ( + vec![KeyParam::UsageCountLimit(42), KeyParam::UsageCountLimit(43)], + "duplicate value", + ), + ( + vec![KeyParam::Nonce(vec![1, 2])], + "not a valid key characteristic", + ), ]; for (characteristics, msg) in tests { let result = crate::tag::characteristics_valid(&characteristics); @@ -80,7 +86,11 @@ fn test_check_begin_params_fail() { vec![KeyParam::Digest(Digest::Sha256), KeyParam::MacLength(160)], "invalid purpose Encrypt", ), - (KeyPurpose::Sign, vec![KeyParam::Digest(Digest::Sha256)], "MissingMacLength"), + ( + KeyPurpose::Sign, + vec![KeyParam::Digest(Digest::Sha256)], + "MissingMacLength", + ), ( KeyPurpose::Sign, vec![KeyParam::Digest(Digest::Sha512), KeyParam::MacLength(160)], @@ -96,7 +106,11 @@ fn test_check_begin_params_fail() { fn test_copyable_tags() { for tag in UNPOLICED_COPYABLE_TAGS { let info = info(*tag).unwrap(); - assert!(info.user_can_specify.0, "tag {:?} not listed as user-specifiable", tag); + assert!( + info.user_can_specify.0, + "tag {:?} not listed as user-specifiable", + tag + ); assert!( info.characteristic == info::Characteristic::KeyMintEnforced || info.characteristic == info::Characteristic::KeystoreEnforced @@ -110,7 +124,12 @@ fn test_copyable_tags() { #[test] fn test_luhn_checksum() { - let tests = vec![(0, 0), (7992739871, 3), (735423462345, 6), (721367498765427, 4)]; + let tests = vec![ + (0, 0), + (7992739871, 3), + (735423462345, 6), + (721367498765427, 4), + ]; for (input, want) in tests { let got = luhn_checksum(input); assert_eq!(got, want, "mismatch for input {}", input); @@ -127,7 +146,10 @@ fn test_increment_imei() { ("7576", ""), ("c328", ""), // Invalid UTF-8 // 721367498765404 => 721367498765412 - ("373231333637343938373635343034", "373231333637343938373635343132"), + ( + "373231333637343938373635343034", + "373231333637343938373635343132", + ), ("39393930", "3130303039"), // String gets longer ]; for (input, want) in tests { diff --git a/libs/rust/derive/tests/integration_test.rs b/libs/rust/derive/tests/integration_test.rs index 5ba34d9..cefe40c 100644 --- a/libs/rust/derive/tests/integration_test.rs +++ b/libs/rust/derive/tests/integration_test.rs @@ -23,12 +23,18 @@ struct NamedFields { #[test] fn test_derive_named_struct_roundtrip() { - let want = NamedFields { i: 42, s: "a string".to_string() }; + let want = NamedFields { + i: 42, + s: "a string".to_string(), + }; let want_value = want.clone().to_cbor_value().unwrap(); let got = NamedFields::from_cbor_value(want_value).unwrap(); assert_eq!(want, got); assert_eq!(NamedFields::cddl_typename().unwrap(), "NamedFields"); - assert_eq!(NamedFields::cddl_schema().unwrap(), "[\n i: int,\n s: tstr,\n]"); + assert_eq!( + NamedFields::cddl_schema().unwrap(), + "[\n i: int,\n s: tstr,\n]" + ); } #[derive(Clone, Debug, PartialEq, Eq, AsCborValue)] @@ -41,7 +47,10 @@ fn test_derive_unnamed_struct_roundtrip() { let got = UnnamedFields::from_cbor_value(want_value).unwrap(); assert_eq!(want, got); assert_eq!(UnnamedFields::cddl_typename().unwrap(), "UnnamedFields"); - assert_eq!(UnnamedFields::cddl_schema().unwrap(), "[\n int,\n tstr,\n]"); + assert_eq!( + UnnamedFields::cddl_schema().unwrap(), + "[\n int,\n tstr,\n]" + ); } #[derive(Clone, Debug, PartialEq, Eq, AsCborValue)] diff --git a/libs/rust/device.prop b/libs/rust/device.prop new file mode 100644 index 0000000..c7f0f86 --- /dev/null +++ b/libs/rust/device.prop @@ -0,0 +1,155 @@ +#################################### +# from generate_common_build_props +# These properties identify this partition image. +#################################### +ro.product.system.brand=Android +ro.product.system.device=generic +ro.product.system.manufacturer=Android +ro.product.system.model=mainline +ro.product.system.name=mainline +ro.system.product.cpu.abilist=arm64-v8a,armeabi-v7a,armeabi +ro.system.product.cpu.abilist32=armeabi-v7a,armeabi +ro.system.product.cpu.abilist64=arm64-v8a +ro.system.build.date=Sun Oct 5 23:32:30 CST 2025 +ro.system.build.date.utc=1759678350 +ro.system.build.fingerprint=google/oriole/oriole:15/BP1A.250505.005/13277524:user/release-keys +ro.system.build.id=BP1A.250505.005 +ro.system.build.tags=release-keys +ro.system.build.type=userdebug +ro.system.build.version.incremental=1759678350 +ro.system.build.version.release=15 +ro.system.build.version.release_or_codename=15 +ro.system.build.version.sdk=35 +ro.system.build.version.sdk_minor=0 +#################################### +# from gen_build_prop.py:generate_build_info +#################################### +# begin build properties +ro.build.fingerprint=google/oriole/oriole:15/BP1A.250505.005/13277524:user/release-keys +ro.build.id=BP1A.250505.005 +ro.build.display.id=BP1A.250505.005 release-keys +ro.build.version.incremental=1759678350 +ro.build.version.sdk=35 +ro.build.version.sdk_minor=0 +ro.build.version.preview_sdk=0 +ro.build.version.preview_sdk_fingerprint=REL +ro.build.version.codename=REL +ro.build.version.all_codenames=REL +ro.build.version.known_codenames=Base,Base11,Cupcake,Donut,Eclair,Eclair01,EclairMr1,Froyo,Gingerbread,GingerbreadMr1,Honeycomb,HoneycombMr1,HoneycombMr2,IceCreamSandwich,IceCreamSandwichMr1,JellyBean,JellyBeanMr1,JellyBeanMr2,Kitkat,KitkatWatch,Lollipop,LollipopMr1,M,N,NMr1,O,OMr1,P,Q,R,S,Sv2,Tiramisu,UpsideDownCake,VanillaIceCream,Baklava +ro.build.version.release=15 +ro.build.version.release_or_codename=15 +ro.build.version.release_or_preview_display=15 +ro.build.version.security_patch=2025-07-01 +ro.build.version.base_os= +ro.build.version.min_supported_target_sdk=28 +ro.build.date=Sun Oct 5 23:32:30 CST 2025 +ro.build.date.utc=1759678350 +ro.build.type=userdebug +ro.build.user=Abcabc +ro.build.host=azure +ro.build.tags=release-keys +ro.build.flavor=lineage_oriole-userdebug +persist.sys.pihooks_FINGERPRINT=google/panther_beta/panther:16/BP31.250523.010/13667654:user/release-keys +persist.sys.pihooks_MODEL=Pixel 7 +# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete, +# use ro.product.cpu.abilist instead. +ro.product.cpu.abi=arm64-v8a +ro.product.locale=en-US +ro.wifi.channels= +# ro.build.product is obsolete; use ro.product.device +ro.build.product=oriole +# Do not try to parse description or thumbprint +ro.build.description=oriole-user 15 BP1A.250505.005 13277524 release-keys +# end build properties +#################################### +# from variable ADDITIONAL_SYSTEM_PROPERTIES +#################################### +ro.treble.enabled=true +ro.llndk.api_level=202404 +ro.actionable_compatible_property.enabled=true +persist.debug.dalvik.vm.core_platform_api_policy=just-warn +ro.postinstall.fstab.prefix=/system +security.perf_harden=1 +ro.appcompat_override.ro.boot.verifiedbootstate=green +ro.allow.mock.location=0 +dalvik.vm.lockprof.threshold=500 +net.bt.name=Android +ro.force.debuggable=0 +#################################### +# from variable PRODUCT_SYSTEM_PROPERTIES +#################################### +ro.custom.blur.enable=true +ro.surface_flinger.supports_background_blur=1 +persist.sys.battery_bypass_supported=false +ro.com.google.clientidbase=android-google +ro.boot.wificountrycode=00 +ro.modversion=10.7 +debug.atrace.tags.enableflags=0 +persist.traced.enable=1 +persist.service.adb.tcp.port=5555 +service.adb.tcp.port=5555 +dalvik.vm.image-dex2oat-Xms=64m +dalvik.vm.image-dex2oat-Xmx=64m +dalvik.vm.dex2oat-Xms=64m +dalvik.vm.dex2oat-Xmx=512m +dalvik.vm.usejit=true +dalvik.vm.dexopt.secondary=true +dalvik.vm.dexopt.thermal-cutoff=2 +dalvik.vm.appimageformat=lz4 +ro.dalvik.vm.native.bridge=0 +pm.dexopt.post-boot=verify +pm.dexopt.first-boot=verify +pm.dexopt.boot-after-ota=verify +pm.dexopt.boot-after-mainline-update=verify +pm.dexopt.install=speed-profile +pm.dexopt.install-fast=skip +pm.dexopt.install-bulk=speed-profile +pm.dexopt.install-bulk-secondary=verify +pm.dexopt.install-bulk-downgraded=verify +pm.dexopt.install-bulk-secondary-downgraded=verify +pm.dexopt.bg-dexopt=speed-profile +pm.dexopt.ab-ota=speed-profile +pm.dexopt.inactive=verify +pm.dexopt.cmdline=verify +pm.dexopt.shared=speed +dalvik.vm.dex2oat-resolve-startup-strings=true +dalvik.vm.dex2oat-max-image-block-size=524288 +dalvik.vm.minidebuginfo=true +dalvik.vm.dex2oat-minidebuginfo=true +dalvik.vm.madvise.vdexfile.size=104857600 +dalvik.vm.madvise.odexfile.size=104857600 +dalvik.vm.madvise.artfile.size=4294967295 +dalvik.vm.usap_pool_enabled=false +dalvik.vm.usap_refill_threshold=1 +dalvik.vm.usap_pool_size_max=3 +dalvik.vm.usap_pool_size_min=1 +dalvik.vm.usap_pool_refill_delay_ms=3000 +dalvik.vm.useartservice=true +dalvik.vm.enable_pr_dexopt=true +ro.cp_system_other_odex=1 +ro.apex.updatable=true +ro.launcher.blur.appLaunch=0 +#################################### +# from variable PRODUCT_SYSTEM_DEFAULT_PROPERTIES +#################################### +ro.com.google.clientidbase=android-google +persist.service.adb.enable=1 +persist.sys.strictmode.disable=true +ro.ota.allow_downgrade=true +audio.service.client_wait_ms=8500 +dalvik.vm.systemuicompilerfilter=speed +debug.sf.enable_transaction_tracing=false +media.recorder.show_manufacturer_and_model=true +net.tethering.noprovisioning=true +# Auto-added by post_process_props.py +persist.sys.usb.config=adb +#################################### +# Adding footer from :applied_backported_fixes +# with path out/soong/.intermediates/build/make/backported_fixes/applied_backported_fixes/gen/applied_backported_fixes.prop +#################################### +# The following backported fixes have been applied +# https://issuetracker.google.com/issues/350037023 with alias 1 +ro.build.backported_fixes.alias_bitset.long_list=2 +# end of file + +keystore.boot_level=30 diff --git a/libs/rust/rsproperties-service/Cargo.toml b/libs/rust/rsproperties-service/Cargo.toml new file mode 100644 index 0000000..403751a --- /dev/null +++ b/libs/rust/rsproperties-service/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rsproperties-service" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[dependencies] +rsproperties = "*" +log = "*" +thiserror = "*" +tokio = "*" +rsactor = "*" + +[dev-dependencies] +env_logger = "*" +ctrlc = "*" +anyhow = "*" +clap = "*" + +[[example]] +name = "example_service" diff --git a/libs/rust/rsproperties-service/README b/libs/rust/rsproperties-service/README new file mode 100644 index 0000000..e938d34 --- /dev/null +++ b/libs/rust/rsproperties-service/README @@ -0,0 +1 @@ +The author didn't publish this library to crates.io. So manually add it here. \ No newline at end of file diff --git a/libs/rust/rsproperties-service/README.md b/libs/rust/rsproperties-service/README.md new file mode 100644 index 0000000..a943b34 --- /dev/null +++ b/libs/rust/rsproperties-service/README.md @@ -0,0 +1,309 @@ +# rsproperties-service + +An async, tokio-based service implementation for Android system properties with Unix domain socket support. + +[![Crates.io](https://img.shields.io/crates/v/rsproperties-service.svg)](https://crates.io/crates/rsproperties-service) +[![Documentation](https://docs.rs/rsproperties-service/badge.svg)](https://docs.rs/rsproperties-service) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) + +## Overview + +`rsproperties-service` provides a high-performance, async property service that mimics Android's property system. It features a Unix domain socket server that handles property operations using an actor-based architecture powered by the `rsactor` framework. + +## Key Features + +- **🔄 Async Operations**: Built on tokio for high-performance async I/O +- **🎭 Actor-Based Architecture**: Uses rsactor for reliable message passing and state management +- **🔌 Unix Domain Socket Server**: Compatible with Android's property service protocol +- **⚡ High Performance**: Non-blocking property operations with concurrent client handling +- **🛡️ Robust Error Handling**: Comprehensive error handling with graceful degradation +- **📂 File-Based Configuration**: Supports property contexts and build.prop file loading +- **🔧 Configurable**: Flexible directory and socket path configuration + +## Architecture + +The service consists of two main components running as separate actors: + +### PropertiesService +- Manages the actual property storage and retrieval +- Loads property contexts from files +- Processes build.prop files +- Handles property addition, updates, and lookups +- Maintains system property state + +### SocketService +- Provides Unix domain socket interface +- Handles client connections and commands +- Implements Android-compatible property service protocol +- Supports SETPROP2 command for property setting +- Manages concurrent client sessions + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +rsproperties-service = "0.1" +``` + +Or add it with the builder feature: + +```toml +[dependencies] +rsproperties-service = { version = "0.1", features = ["builder"] } +``` + +## Quick Start + +### Basic Service Setup + +```rust +use rsproperties_service; +use std::path::PathBuf; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create configuration + let config = rsproperties::PropertyConfig::with_both_dirs( + PathBuf::from("/tmp/properties"), + PathBuf::from("/tmp/sockets") + ); + + // Start the services + let (socket_service, properties_service) = rsproperties_service::run( + config, + vec![], // property_contexts_files + vec![], // build_prop_files + ).await?; + + println!("Services started successfully!"); + + // Keep services running + tokio::select! { + _ = tokio::signal::ctrl_c() => { + println!("Shutdown signal received"); + } + result = socket_service.join_handle => { + if let Err(e) = result { + eprintln!("Socket service error: {}", e); + } + } + result = properties_service.join_handle => { + if let Err(e) = result { + eprintln!("Properties service error: {}", e); + } + } + } + + Ok(()) +} +``` + +### Advanced Configuration with Files + +```rust +use rsproperties_service; +use std::path::PathBuf; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup directories + let properties_dir = PathBuf::from("/system/etc/properties"); + let socket_dir = PathBuf::from("/dev/socket"); + + // Configure property context files + let property_contexts = vec![ + PathBuf::from("/system/etc/selinux/property_contexts"), + PathBuf::from("/vendor/etc/selinux/vendor_property_contexts"), + ]; + + // Configure build.prop files + let build_props = vec![ + PathBuf::from("/system/build.prop"), + PathBuf::from("/vendor/build.prop"), + PathBuf::from("/product/build.prop"), + ]; + + let config = rsproperties::PropertyConfig::with_both_dirs( + properties_dir, + socket_dir + ); + + // Start services with configuration files + let (socket_service, properties_service) = rsproperties_service::run( + config, + property_contexts, + build_props, + ).await?; + + // Services are now running with full Android-compatible configuration + tokio::join!( + socket_service.join_handle, + properties_service.join_handle + ); + + Ok(()) +} +``` + +## Service Components + +### ServiceContext + +Each service returns a `ServiceContext` containing: + +```rust +pub struct ServiceContext { + pub actor_ref: ActorRef, // Reference for sending messages + pub join_handle: JoinHandle>, // Handle for awaiting completion +} +``` + +### Message Types + +The services communicate using these message types: + +- **ReadyMessage**: Check if service is ready +- **PropertyMessage**: Set/update property values + +```rust +// Check if service is ready +let is_ready = properties_service.actor_ref.ask(ReadyMessage {}).await?; + +// Send property update +let success = properties_service.actor_ref.ask(PropertyMessage { + key: "my.app.debug".to_string(), + value: "true".to_string(), +}).await?; +``` + +## Protocol Compatibility + +The socket service implements the Android property service protocol: + +- **Socket Names**: Uses standard Android socket names (`property_service`, `property_service_for_system`) +- **Commands**: Supports `PROP_MSG_SETPROP2` (0x00020001) command +- **Response Codes**: Returns `PROP_SUCCESS` (0) or `PROP_ERROR` (-1) +- **Message Format**: Compatible with Android's binary protocol + +## Error Handling + +The service provides comprehensive error handling: + +```rust +match rsproperties_service::run(config, contexts, props).await { + Ok((socket_service, properties_service)) => { + // Services started successfully + println!("All services running"); + } + Err(e) => { + eprintln!("Failed to start services: {}", e); + // Handle specific error types + if e.to_string().contains("Permission denied") { + eprintln!("Check directory permissions"); + } + } +} +``` + +## Performance Features + +- **Concurrent Connections**: Each client connection handled in separate tasks +- **Non-blocking I/O**: All operations use async/await for optimal performance +- **Memory Efficient**: Property data shared between services using actor references +- **Fast Lookups**: Optimized property storage for quick access + +## Directory Structure + +The service expects this directory layout: + +``` +properties_dir/ +├── property_info # Property metadata (generated) +├── properties_serial # Property versioning +└── u:object_r:*:s0 # SELinux context files + +socket_dir/ +├── property_service # Main property socket +└── property_service_for_system # System property socket +``` + +## Security Features + +- **Path Validation**: Validates all file and directory paths +- **Size Limits**: Enforces reasonable limits on property names (1KB) and values (8KB) +- **SELinux Support**: Handles SELinux property contexts when provided +- **Permission Handling**: Respects file system permissions + +## Logging + +The service provides detailed logging at multiple levels: + +```rust +// Enable logging +env_logger::Builder::from_env( + env_logger::Env::default().default_filter_or("info") +).init(); +``` + +Log levels: +- **ERROR**: Service failures, connection errors +- **WARN**: Graceful shutdowns, configuration issues +- **INFO**: Service lifecycle, property operations +- **DEBUG**: Client connections, message details +- **TRACE**: Protocol-level details, fine-grained operations + +## Examples + +### Running the Example Service + +```bash +cargo run --example example_service -- \ + --properties-dir /tmp/test_properties \ + --socket-dir /tmp/test_sockets +``` + +### Testing with netcat + +```bash +# Connect to the property service socket +nc -U /tmp/test_sockets/property_service +``` + +## Testing + +Run the comprehensive test suite: + +```bash +# Run all tests +cargo test + +# Run with logging +RUST_LOG=debug cargo test -- --nocapture + +# Run specific test category +cargo test integration_tests +cargo test performance_tests +``` + +## Dependencies + +- **tokio**: Async runtime and I/O +- **rsactor**: Actor framework for message passing +- **rsproperties**: Core property system implementation +- **log**: Logging framework +- **thiserror**: Error handling + +## License + +Licensed under the Apache License, Version 2.0. See [LICENSE](../LICENSE) for details. + +## Contributing + +Contributions are welcome! Please see the main [rsproperties](../README.md) project for contribution guidelines. + +## Related Projects + +- **[rsproperties](../rsproperties/)**: Core property system library +- **Android Property System**: Original implementation this project emulates diff --git a/libs/rust/rsproperties-service/examples/example_service.rs b/libs/rust/rsproperties-service/examples/example_service.rs new file mode 100644 index 0000000..b318097 --- /dev/null +++ b/libs/rust/rsproperties-service/examples/example_service.rs @@ -0,0 +1,80 @@ +use std::fs::{create_dir_all, remove_dir_all}; +use std::path::PathBuf; + +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(name = "example_service")] +#[command(about = "Example service for rsproperties-service")] +struct Args { + /// Properties directory path + #[arg(long, help = "Directory path for system properties")] + properties_dir: Option, + + /// Socket directory path + #[arg(long, help = "Directory path for property service sockets")] + socket_dir: Option, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Parse command line arguments + let args = Args::parse(); + + // Initialize logging + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + + // Setup directories + let properties_dir = args + .properties_dir + .unwrap_or_else(|| PathBuf::from("__properties__")); + let socket_dir = args + .socket_dir + .unwrap_or_else(|| properties_dir.join("sockets")); + + // Clean and create directories + let _ = remove_dir_all(&properties_dir); + let _ = remove_dir_all(&socket_dir); + create_dir_all(&properties_dir)?; + create_dir_all(&socket_dir)?; + + println!("📁 Created directories:"); + println!(" Properties: {properties_dir:?}"); + println!(" Sockets: {socket_dir:?}"); + + // Create PropertyConfig + let config = rsproperties::PropertyConfig::with_both_dirs(properties_dir, socket_dir); + + println!("🚀 Starting rsproperties services..."); + + // Initialize the services + let (socket_service, properties_service) = rsproperties_service::run( + config, + vec![], // property_contexts_files + vec![], // build_prop_files + ) + .await?; + + println!("✅ Services started successfully!"); + println!("🔄 Services are running. Press Ctrl+C to stop."); + + // Handle graceful shutdown + tokio::select! { + _ = tokio::signal::ctrl_c() => { + println!("\n🛑 Shutdown signal received..."); + } + result1 = socket_service.join_handle => { + if let Err(e) = result1 { + eprintln!("❌ Socket service error: {e}"); + } + } + result2 = properties_service.join_handle => { + if let Err(e) = result2 { + eprintln!("❌ Properties service error: {e}"); + } + } + } + + println!("👋 Services stopped."); + Ok(()) +} diff --git a/libs/rust/rsproperties-service/src/lib.rs b/libs/rust/rsproperties-service/src/lib.rs new file mode 100644 index 0000000..516b86e --- /dev/null +++ b/libs/rust/rsproperties-service/src/lib.rs @@ -0,0 +1,86 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Async property socket service implementation for Android system properties +//! +//! This crate provides a tokio-based async implementation of the Android property +//! socket service, allowing for non-blocking property value reception and parsing. + +use std::path::PathBuf; + +use rsactor::{Actor, ActorRef, ActorResult}; + +pub mod properties_service; +pub mod socket_service; + +pub use socket_service::{SocketService, SocketServiceArgs}; + +pub use properties_service::PropertiesService; + +pub(crate) struct ReadyMessage {} + +#[derive(Debug, Clone)] +pub(crate) struct PropertyMessage { + pub key: String, + pub value: String, +} + +pub struct ServiceContext { + pub actor_ref: ActorRef, + pub join_handle: tokio::task::JoinHandle>, +} + +/// Runs the property socket service with the given configuration. +/// +/// # Requirements +/// All folders specified in the PropertyConfig must be valid and accessible +/// for the function to execute successfully. +pub async fn run( + config: rsproperties::PropertyConfig, + property_contexts_files: Vec, + build_prop_files: Vec, +) -> Result< + ( + ServiceContext, + ServiceContext, + ), + Box, +> { + rsproperties::init(config); + + let properties_service = properties_service::run(property_contexts_files, build_prop_files); + + // Initialize the socket service + let socket_service = socket_service::run(SocketServiceArgs { + socket_dir: rsproperties::socket_dir().to_path_buf(), + properties_service: properties_service.actor_ref.clone(), + }); + + let _ = socket_service + .actor_ref + .ask(ReadyMessage {}) + .await + .map_err(|e| format!("Failed to start socket service: {e}"))?; + let _ = properties_service + .actor_ref + .ask(ReadyMessage {}) + .await + .map_err(|e| format!("Failed to start properties service: {e}"))?; + + Ok((socket_service, properties_service)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_property_message() { + let msg = PropertyMessage { + key: "test.key".to_string(), + value: "test.value".to_string(), + }; + assert_eq!(msg.key, "test.key"); + assert_eq!(msg.value, "test.value"); + } +} diff --git a/libs/rust/rsproperties-service/src/properties_service.rs b/libs/rust/rsproperties-service/src/properties_service.rs new file mode 100644 index 0000000..01b49c3 --- /dev/null +++ b/libs/rust/rsproperties-service/src/properties_service.rs @@ -0,0 +1,166 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +use rsactor::{Actor, ActorRef, ActorWeak}; +use rsproperties::{build_trie, load_properties_from_file, PropertyInfoEntry, SystemProperties}; + +pub struct PropertiesServiceArgs { + property_contexts_files: Vec, + build_prop_files: Vec, +} + +pub struct PropertiesService { + system_properties: SystemProperties, +} + +impl PropertiesService {} + +impl Actor for PropertiesService { + type Args = PropertiesServiceArgs; + type Error = std::io::Error; + + async fn on_start( + args: Self::Args, + _actor_ref: &rsactor::ActorRef, + ) -> std::result::Result { + let mut property_infos = Vec::new(); + for file in args.property_contexts_files { + let (mut property_info, errors) = + PropertyInfoEntry::parse_from_file(&file, false).unwrap(); + if !errors.is_empty() { + log::error!("{errors:?}"); + } + property_infos.append(&mut property_info); + } + + let data: Vec = + build_trie(&property_infos, "u:object_r:build_prop:s0", "string").unwrap(); + + let dir = rsproperties::properties_dir(); + File::create(dir.join("property_info")) + .unwrap() + .write_all(&data) + .unwrap(); + + let mut properties = HashMap::new(); + for file in args.build_prop_files { + load_properties_from_file(&file, None, "u:r:init:s0", &mut properties).unwrap(); + } + + let mut system_properties = SystemProperties::new_area(dir).unwrap_or_else(|e| { + panic!("Cannot create system properties: {e}. Please check if {dir:?} exists.") + }); + for (key, value) in properties.iter() { + match system_properties.find(key.as_str()).unwrap() { + Some(prop_ref) => { + system_properties.update(&prop_ref, value.as_str()).unwrap(); + } + None => { + system_properties.add(key.as_str(), value.as_str()).unwrap(); + } + } + } + + Ok(PropertiesService { + // Initialize the service with the provided arguments + system_properties, + }) + } + + async fn on_stop( + &mut self, + _actor_weak: &ActorWeak, + killed: bool, + ) -> std::result::Result<(), Self::Error> { + log::warn!("====================================="); + log::warn!(" PROPERTIES SERVICE SHUTDOWN "); + log::warn!("====================================="); + + if killed { + log::error!("*** FORCED TERMINATION *** PropertiesService is being killed, cleaning up resources."); + } else { + log::warn!("*** GRACEFUL SHUTDOWN *** PropertiesService is stopping gracefully."); + } + + // Perform any necessary cleanup here + // For example, you might want to save the current state or close any open files + + log::warn!("PropertiesService cleanup completed - SERVICE TERMINATED"); + log::warn!("====================================="); + + Ok(()) + } +} + +impl rsactor::Message for PropertiesService { + type Reply = bool; + + async fn handle( + &mut self, + _message: crate::ReadyMessage, + _actor_ref: &ActorRef, + ) -> Self::Reply { + true + } +} + +impl rsactor::Message for PropertiesService { + type Reply = bool; + + async fn handle( + &mut self, + message: crate::PropertyMessage, + _actor_ref: &ActorRef, + ) -> Self::Reply { + log::debug!("Handling property message: {message:?}"); + // Process the property message + let key = message.key; + let value = message.value; + + // Check if the property exists in the system properties + match self.system_properties.find(&key) { + Ok(Some(prop_ref)) => { + // Update the existing property + if let Err(e) = self.system_properties.update(&prop_ref, &value) { + log::error!("Failed to update property '{key}': {e}"); + false // Indicate failure + } else { + log::info!("Updated property: {key} = {value}"); + true // Indicate success + } + } + Ok(None) => { + // Property does not exist, add it + if let Err(e) = self.system_properties.add(&key, &value) { + log::error!("Failed to add property '{key}': {e}"); + false // Indicate failure + } else { + log::info!("Added property: {key} = {value}"); + true // Indicate success + } + } + Err(e) => { + log::error!("Failed to find property '{key}': {e}"); + false // Indicate failure + } + } + } +} + +pub fn run( + property_contexts_files: Vec, + build_prop_files: Vec, +) -> crate::ServiceContext { + let args = PropertiesServiceArgs { + property_contexts_files, + build_prop_files, + }; + + let (actor_ref, join_handle) = rsactor::spawn(args); + crate::ServiceContext { + actor_ref, + join_handle, + } +} diff --git a/libs/rust/rsproperties-service/src/socket_service.rs b/libs/rust/rsproperties-service/src/socket_service.rs new file mode 100644 index 0000000..6601a8c --- /dev/null +++ b/libs/rust/rsproperties-service/src/socket_service.rs @@ -0,0 +1,378 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +use std::{fs, path::PathBuf}; + +use log::{debug, error, info, trace, warn}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{UnixListener, UnixStream}; + +use rsactor::{Actor, ActorRef, ActorWeak}; + +use rsproperties::errors::*; + +const PROP_MSG_SETPROP2: u32 = 0x00020001; +const PROP_SUCCESS: i32 = 0; +const PROP_ERROR: i32 = -1; + +pub struct SocketServiceArgs { + pub socket_dir: PathBuf, + pub properties_service: ActorRef, +} + +// Run the service in a separate task +/// This function runs the socket service by spawning a new actor with the provided arguments. +/// +/// # Returns +/// A reference to the spawned actor that can be used to interact with the socket service. +/// The actor can be stopped by calling `actor_ref.stop()` when the service is no longer needed. +/// +pub fn run(args: SocketServiceArgs) -> crate::ServiceContext { + let (actor_ref, join_handle) = rsactor::spawn(args); + crate::ServiceContext { + actor_ref, + join_handle, + } +} + +/// Tokio-based property socket service +pub struct SocketService { + socket_dir: PathBuf, + property_listener: UnixListener, + system_listener: UnixListener, + properties_service: ActorRef, +} + +impl Actor for SocketService { + type Args = SocketServiceArgs; + type Error = rsproperties::errors::Error; + + async fn on_start( + args: Self::Args, + _actor_ref: &ActorRef, + ) -> std::result::Result { + // Create parent directory if it doesn't exist + if !args.socket_dir.exists() { + debug!("Creating parent directory: {:?}", args.socket_dir); + fs::create_dir_all(&args.socket_dir).map_err(rsproperties::errors::Error::new_io)?; + } + + let property_socket_path = args + .socket_dir + .join(rsproperties::PROPERTY_SERVICE_SOCKET_NAME); + let system_socket_path = args + .socket_dir + .join(rsproperties::PROPERTY_SERVICE_FOR_SYSTEM_SOCKET_NAME); + // Remove existing socket files if they exist + if property_socket_path.exists() { + debug!( + "Removing existing property socket file: {}", + property_socket_path.display() + ); + fs::remove_file(&property_socket_path).map_err(rsproperties::errors::Error::new_io)?; + } + if system_socket_path.exists() { + debug!( + "Removing existing system socket file: {}", + system_socket_path.display() + ); + fs::remove_file(&system_socket_path).map_err(rsproperties::errors::Error::new_io)?; + } + info!( + "Property socket services successfully created at: {} and {}", + property_socket_path.display(), + system_socket_path.display() + ); + // Bind both sockets + trace!( + "Binding property service Unix domain socket: {}", + property_socket_path.display() + ); + let property_listener = UnixListener::bind(&property_socket_path) + .map_err(rsproperties::errors::Error::new_io)?; + trace!( + "Binding system property service Unix domain socket: {}", + system_socket_path.display() + ); + let system_listener = + UnixListener::bind(&system_socket_path).map_err(rsproperties::errors::Error::new_io)?; + info!("AsyncPropertySocketService started successfully"); + + Ok(Self { + socket_dir: args.socket_dir, + property_listener, + system_listener, + properties_service: args.properties_service, + }) + } + + async fn on_run( + &mut self, + _actor_weak: &ActorWeak, + ) -> std::result::Result<(), Self::Error> { + tokio::select! { + _ = Self::handle_socket_connections(&self.property_listener, self.properties_service.clone()) => { + trace!("Property socket service task completed"); + } + _ = Self::handle_socket_connections(&self.system_listener, self.properties_service.clone()) => { + trace!("System property socket service task completed"); + } + } + Ok(()) + } + + async fn on_stop( + &mut self, + _actor_weak: &ActorWeak, + killed: bool, + ) -> std::result::Result<(), Self::Error> { + warn!("====================================="); + warn!(" SOCKET SERVICE SHUTDOWN "); + warn!("====================================="); + + if killed { + error!( + "*** FORCED TERMINATION *** SocketService is being killed, cleaning up resources." + ); + } else { + warn!("*** GRACEFUL SHUTDOWN *** SocketService is stopping gracefully."); + } + + warn!("SocketService cleanup completed - SERVICE TERMINATED"); + warn!("====================================="); + + Ok(()) + } +} + +impl rsactor::Message for SocketService { + type Reply = bool; + + async fn handle( + &mut self, + _message: crate::ReadyMessage, + _actor_ref: &ActorRef, + ) -> Self::Reply { + true + } +} + +impl SocketService { + /// Handles socket connections for a specific socket type + async fn handle_socket_connections( + listener: &UnixListener, + service: ActorRef, + ) -> Result<()> { + // Try to accept a connection with timeout + let connection_result = listener.accept().await; + + match connection_result { + Ok((stream, _)) => { + // Clone sender for this connection + let connection_sender = service.clone(); + + // Handle each connection in a separate task + tokio::spawn(async move { + if let Err(e) = Self::handle_client(stream, connection_sender).await { + error!("Error handling client: {e}"); + } + }); + } + Err(e) => { + error!("Error accepting connection: {e}"); + } + } + Ok(()) + } + + /// Handles a client connection + async fn handle_client( + mut stream: UnixStream, + service: ActorRef, + ) -> Result<()> { + trace!("Handling new client connection"); + + // Read the command (u32) + let mut cmd_buf = [0u8; 4]; + stream + .read_exact(&mut cmd_buf) + .await + .map_err(rsproperties::errors::Error::new_io)?; + let cmd = u32::from_ne_bytes(cmd_buf); + + debug!("Received command: 0x{cmd:08X}"); + + match cmd { + PROP_MSG_SETPROP2 => { + trace!("Processing SETPROP2 command"); + Self::handle_setprop2(&mut stream, service).await?; + } + _ => { + warn!("Unknown command received: 0x{cmd:08X}"); + Self::send_response(&mut stream, PROP_ERROR).await?; + return Err(rsproperties::errors::Error::new_parse(format!( + "Unknown command: 0x{cmd:08X}" + ))); + } + } + + trace!("Client connection handled successfully"); + Ok(()) + } + + /// Handles SETPROP2 command + async fn handle_setprop2( + stream: &mut UnixStream, + service: ActorRef, + ) -> Result<()> { + trace!("Handling SETPROP2 request"); + + // Read name length and name + let name_len = Self::read_u32(stream).await?; + trace!("Name length: {name_len}"); + + if name_len > 1024 { + // Reasonable limit + error!("Name length too large: {name_len}"); + Self::send_response(stream, PROP_ERROR).await?; + return Err(rsproperties::errors::Error::new_file_validation(format!( + "Name length too large: {name_len}" + ))); + } + + let name = Self::read_string(stream, name_len as usize).await?; + debug!("Property name: '{name}'"); + + // Read value length and value + let value_len = Self::read_u32(stream).await?; + trace!("Value length: {value_len}"); + + if value_len > 8192 { + // Reasonable limit for property values + error!("Value length too large: {value_len}"); + Self::send_response(stream, PROP_ERROR).await?; + return Err(rsproperties::errors::Error::new_file_validation(format!( + "Value length too large: {value_len}" + ))); + } + + let value = Self::read_string(stream, value_len as usize).await?; + debug!("Property value: '{value}'"); + + // Process the property setting + info!("Successfully set property: '{name}' = '{value}'"); + + // Send property data through channel if sender is available + let property_msg = crate::PropertyMessage { + key: name.clone(), + value: value.clone(), + }; + + match service.ask(property_msg).await { + Ok(true) => { + debug!("Property message sent successfully: '{name}' = '{value}'"); + Self::send_response(stream, PROP_SUCCESS).await?; + } + Ok(false) => { + warn!("Property message was not processed by service: '{name}' = '{value}'"); + // Don't fail the operation if service doesn't process it + Self::send_response(stream, PROP_ERROR).await?; + } + Err(e) => { + error!("Failed to send property message through channel: {e}"); + // Don't fail the operation if channel send fails + Self::send_response(stream, PROP_ERROR).await?; + } + } + + Ok(()) + } + + /// Reads a u32 value from the stream + async fn read_u32(stream: &mut UnixStream) -> Result { + let mut buf = [0u8; 4]; + stream + .read_exact(&mut buf) + .await + .map_err(rsproperties::errors::Error::new_io)?; + Ok(u32::from_ne_bytes(buf)) + } + + /// Reads a string of specified length from the stream + async fn read_string(stream: &mut UnixStream, len: usize) -> Result { + if len == 0 { + return Ok(String::new()); + } + + let mut buf = vec![0u8; len]; + stream + .read_exact(&mut buf) + .await + .map_err(rsproperties::errors::Error::new_io)?; + + // Remove null terminator if present + if let Some(null_pos) = buf.iter().position(|&x| x == 0) { + buf.truncate(null_pos); + } + + String::from_utf8(buf).map_err(|e| rsproperties::errors::Error::new_encoding(e.to_string())) + } + + /// Sends a response to the client + async fn send_response(stream: &mut UnixStream, response: i32) -> Result<()> { + trace!("Sending response: {response}"); + stream + .write_all(&response.to_ne_bytes()) + .await + .map_err(rsproperties::errors::Error::new_io)?; + stream + .flush() + .await + .map_err(rsproperties::errors::Error::new_io)?; + trace!("Response sent successfully"); + Ok(()) + } +} + +impl Drop for SocketService { + fn drop(&mut self) { + debug!("Cleaning up async socket service"); + + // Remove socket files + let property_socket_path = self + .socket_dir + .join(rsproperties::PROPERTY_SERVICE_SOCKET_NAME); + if property_socket_path.exists() { + if let Err(e) = fs::remove_file(&property_socket_path) { + warn!( + "Failed to remove property socket file {}: {}", + property_socket_path.display(), + e + ); + } else { + debug!( + "Property socket file removed: {}", + property_socket_path.display() + ); + } + } + + let system_socket_path = self + .socket_dir + .join(rsproperties::PROPERTY_SERVICE_FOR_SYSTEM_SOCKET_NAME); + if system_socket_path.exists() { + if let Err(e) = fs::remove_file(&system_socket_path) { + warn!( + "Failed to remove system socket file {}: {}", + system_socket_path.display(), + e + ); + } else { + debug!( + "System socket file removed: {}", + system_socket_path.display() + ); + } + } + } +} diff --git a/libs/rust/rsproperties-service/tests/common.rs b/libs/rust/rsproperties-service/tests/common.rs new file mode 100644 index 0000000..4de9163 --- /dev/null +++ b/libs/rust/rsproperties-service/tests/common.rs @@ -0,0 +1,74 @@ +use std::fs::{create_dir_all, remove_dir_all}; +use std::{path::PathBuf, str::FromStr}; + +use tokio::sync::OnceCell; + +use rsproperties::PropertyConfig; + +use rsproperties_service::{PropertiesService, ServiceContext, SocketService}; + +use rsactor::ActorRef; + +pub static TEST_PROPERTIES_DIR: &str = "__properties__"; + +static SERVICES: OnceCell<(ActorRef, ActorRef)> = + OnceCell::const_new(); + +async fn inti() -> ( + ServiceContext, + ServiceContext, +) { + let _ = env_logger::builder().is_test(true).try_init(); + + let properties_dir = + PathBuf::from_str(TEST_PROPERTIES_DIR).expect("Failed to parse properties directory path"); + let socket_dir = properties_dir.join("sockets"); + + remove_dir_all(&properties_dir).unwrap_or_default(); + remove_dir_all(&socket_dir).unwrap_or_default(); + create_dir_all(&properties_dir).expect("Failed to create properties directory"); + create_dir_all(&socket_dir).expect("Failed to create socket directory"); + + let config = PropertyConfig::with_both_dirs(properties_dir, socket_dir); + + rsproperties_service::run(config, vec![], vec![]) + .await + .expect("Failed to start services") +} + +pub async fn init_test() -> (ActorRef, ActorRef) { + SERVICES + .get_or_init(|| async { + let (sender, receiver) = tokio::sync::oneshot::channel::<( + ActorRef, + ActorRef, + )>(); + + std::thread::spawn(move || { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Failed to create Tokio runtime"); + + runtime.block_on(async { + let services = inti().await; + let _ = + sender.send((services.0.actor_ref.clone(), services.1.actor_ref.clone())); + + let (res1, res2) = + tokio::join!(services.0.join_handle, services.1.join_handle,); + // Handle potential errors from join handles + if let Err(e) = res1 { + eprintln!("Socket service error: {e}"); + } + if let Err(e) = res2 { + eprintln!("Properties service error: {e}"); + } + }); + }); + + receiver.await.expect("Failed to receive services") + }) + .await + .clone() +} diff --git a/libs/rust/rsproperties-service/tests/comprehensive_api_tests.rs b/libs/rust/rsproperties-service/tests/comprehensive_api_tests.rs new file mode 100644 index 0000000..61a0e27 --- /dev/null +++ b/libs/rust/rsproperties-service/tests/comprehensive_api_tests.rs @@ -0,0 +1,482 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Comprehensive integration tests for new property APIs +//! +//! These tests verify the complete integration between: +//! - set() function with type conversion +//! - get_parsed() and get_parsed_with_default() functions +//! - Type safety and conversion accuracy +//! - Error handling and edge cases +//! - Performance and consistency +#![allow(clippy::approx_constant)] + +#[path = "common.rs"] +mod common; +use common::init_test; + +async fn setup_test_env() { + let _ = env_logger::builder().is_test(true).try_init(); + init_test().await; +} + +/// Test the complete workflow: set numeric types and parse them back +#[tokio::test] +async fn test_numeric_round_trip() -> anyhow::Result<()> { + setup_test_env().await; + + let test_cases = [ + ("i8", i8::MIN as i64, i8::MAX as i64), + ("i16", i16::MIN as i64, i16::MAX as i64), + ("i32", i32::MIN as i64, i32::MAX as i64), + ("u8", 0, u8::MAX as i64), + ("u16", 0, u16::MAX as i64), + ("u32", 0, u32::MAX as i64), + ]; + + for (type_name, min_val, max_val) in test_cases.iter() { + // Test minimum value + let prop_name_min = format!("test.round_trip.{type_name}_min"); + rsproperties::set(&prop_name_min, min_val)?; + let parsed_min: i64 = rsproperties::get(&prop_name_min)?; + assert_eq!( + parsed_min, *min_val, + "Round trip failed for {type_name} min value" + ); + + // Test maximum value + let prop_name_max = format!("test.round_trip.{type_name}_max"); + rsproperties::set(&prop_name_max, max_val)?; + let parsed_max: i64 = rsproperties::get(&prop_name_max)?; + assert_eq!( + parsed_max, *max_val, + "Round trip failed for {type_name} max value" + ); + } + + Ok(()) +} + +/// Test floating point precision through set/get cycle +#[tokio::test] +async fn test_float_precision_round_trip() -> anyhow::Result<()> { + setup_test_env().await; + + let test_cases = [ + ("pi", std::f64::consts::PI), + ("e", std::f64::consts::E), + ("small", 1e-10), + ("large", 1e10), + ("negative", -3.14159), + ("zero", 0.0), + ]; + + for (name, original_value) in test_cases.iter() { + let prop_name = format!("test.float_precision.{name}"); + + rsproperties::set(&prop_name, original_value)?; + let parsed_value: f64 = rsproperties::get(&prop_name)?; + + // For floating point, we need to account for string representation precision + let diff = (parsed_value - original_value).abs(); + let epsilon = if original_value.abs() > 1.0 { + original_value.abs() * 1e-10 + } else { + 1e-10 + }; + + assert!( + diff < epsilon, + "Float precision lost for {name}: original={original_value}, parsed={parsed_value}, diff={diff}" + ); + } + + Ok(()) +} + +/// Test that set produces consistent results for string values +#[tokio::test] +async fn test_string_consistency() -> anyhow::Result<()> { + setup_test_env().await; + + let test_strings = [ + "simple_string", + "string with spaces", + "unicode_测试_🚀", + "", + "123456", + "true", + "false", + "3.14159", + "special!@#$%^&*()", + ]; + + for (i, test_string) in test_strings.iter().enumerate() { + let prop_name = format!("test.string_consistency.set_{i}"); + + // Skip empty strings as they may not be supported by the underlying property system + if test_string.is_empty() { + continue; + } + + // Set using set function (which handles Display types including &str) + rsproperties::set(&prop_name, test_string)?; + + // Get the value back + let retrieved_value: String = rsproperties::get(&prop_name)?; + + assert_eq!( + retrieved_value, *test_string, + "String value not preserved for: '{test_string}'" + ); + } + + Ok(()) +} + +/// Test mixed data type operations on the same property +#[tokio::test] +async fn test_property_type_overwriting() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.type_overwriting.mixed"; + + // Start with integer + rsproperties::set(prop_name, &42i32)?; + let as_int: i32 = rsproperties::get(prop_name)?; + assert_eq!(as_int, 42); + + // Overwrite with float + rsproperties::set(prop_name, &3.14f64)?; + let as_float: f64 = rsproperties::get(prop_name)?; + assert!((as_float - 3.14).abs() < f64::EPSILON); + + // Overwrite with boolean + rsproperties::set(prop_name, &true)?; + let as_string: String = rsproperties::get(prop_name)?; + assert_eq!(as_string, "true"); + + // Try to parse as integer (should fail) + let int_result: Result = rsproperties::get(prop_name); + assert!(int_result.is_err()); + + // Overwrite with string number + rsproperties::set(prop_name, "999")?; + let back_to_int: i32 = rsproperties::get(prop_name)?; + assert_eq!(back_to_int, 999); + + Ok(()) +} + +/// Test error propagation and handling across the API +#[tokio::test] +async fn test_error_handling_integration() -> anyhow::Result<()> { + setup_test_env().await; + + // Test 1: Parse non-existent property + let non_existent = "definitely.does.not.exist.anywhere"; + let result: Result = rsproperties::get(non_existent); + assert!(result.is_err()); + + // But get_or should work + let with_default: i32 = rsproperties::get_or(non_existent, 42); + assert_eq!(with_default, 42); + + // Test 2: Parse invalid format + let prop_name = "test.error.invalid_format"; + rsproperties::set(prop_name, "not_a_number")?; + + let parse_result: Result = rsproperties::get(prop_name); + assert!(parse_result.is_err()); + + // But get_or should return default + let default_fallback: i32 = rsproperties::get_or(prop_name, 999); + assert_eq!(default_fallback, 999); + + // Test 3: Parse empty string + rsproperties::set(prop_name, "")?; + let empty_result: Result = rsproperties::get(prop_name); + assert!(empty_result.is_err()); + + Ok(()) +} + +/// Test performance and consistency under concurrent operations +#[tokio::test] +async fn test_concurrent_mixed_operations() -> anyhow::Result<()> { + setup_test_env().await; + + let num_tasks = 20; + let operations_per_task = 10; + + let handles: Vec<_> = (0..num_tasks) + .map(|task_id| { + tokio::spawn(async move { + for op_id in 0..operations_per_task { + let prop_name = format!("test.concurrent.task_{task_id}.op_{op_id}"); + + // Mix of different operations + match op_id % 4 { + 0 => { + // Set and parse integer + let value = task_id * 1000 + op_id; + rsproperties::set(&prop_name, &value)?; + let parsed: i32 = rsproperties::get(&prop_name)?; + assert_eq!(parsed, value); + } + 1 => { + // Set and parse float + let value = (task_id as f64) + (op_id as f64) * 0.1; + rsproperties::set(&prop_name, &value)?; + let parsed: f64 = rsproperties::get(&prop_name)?; + assert!((parsed - value).abs() < f64::EPSILON); + } + 2 => { + // Set string and get + let value = format!("task_{task_id}_{op_id}"); + rsproperties::set(&prop_name, &value)?; + let retrieved: String = rsproperties::get(&prop_name)?; + assert_eq!(retrieved, value); + } + 3 => { + // Set boolean and parse + let value = (task_id + op_id) % 2 == 0; + rsproperties::set(&prop_name, &value)?; + let as_string: String = rsproperties::get(&prop_name)?; + assert_eq!(as_string, value.to_string()); + } + _ => unreachable!(), + } + } + + anyhow::Ok(()) + }) + }) + .collect(); + + // Wait for all tasks to complete + for handle in handles { + handle.await??; + } + + Ok(()) +} + +/// Test edge cases for numeric parsing +#[tokio::test] +async fn test_numeric_edge_cases() -> anyhow::Result<()> { + setup_test_env().await; + + // Test boundary values for different integer types + let edge_cases = [ + ("zero", "0", 0i64), + ("positive_small", "1", 1i64), + ("negative_small", "-1", -1i64), + ("i32_max", &i32::MAX.to_string(), i32::MAX as i64), + ("i32_min", &i32::MIN.to_string(), i32::MIN as i64), + ("large_positive", "999999999", 999999999i64), + ("large_negative", "-999999999", -999999999i64), + ]; + + for (name, string_value, expected) in edge_cases.iter() { + let prop_name = format!("test.edge_cases.{name}"); + + rsproperties::set(&prop_name, string_value)?; + let parsed: i64 = rsproperties::get(&prop_name)?; + assert_eq!(parsed, *expected, "Edge case failed for {name}"); + + // Also test with get_or + let with_default: i64 = rsproperties::get_or(&prop_name, 42); + assert_eq!( + with_default, *expected, + "get_parsed_with_default failed for {name}" + ); + } + + Ok(()) +} + +/// Test boolean parsing with various string representations +#[tokio::test] +async fn test_boolean_string_variations() -> anyhow::Result<()> { + setup_test_env().await; + + // Note: Rust's bool::from_str() only accepts "true" and "false" (case-sensitive) + let valid_true_cases = [("lowercase_true", "true", true)]; + + let valid_false_cases = [("lowercase_false", "false", false)]; + + let invalid_cases = [ + ("uppercase_true", "TRUE"), + ("uppercase_false", "FALSE"), + ("mixed_case_true", "True"), + ("mixed_case_false", "False"), + ("numeric_one", "1"), + ("numeric_zero", "0"), + ("yes", "yes"), + ("no", "no"), + ("on", "on"), + ("off", "off"), + ]; + + // Test valid true cases + for (name, string_value, expected) in valid_true_cases.iter() { + let prop_name = format!("test.bool_valid.{name}"); + rsproperties::set(&prop_name, string_value)?; + let parsed: bool = rsproperties::get(&prop_name)?; + assert_eq!(parsed, *expected, "Valid bool case failed for {name}"); + } + + // Test valid false cases + for (name, string_value, expected) in valid_false_cases.iter() { + let prop_name = format!("test.bool_valid.{name}"); + rsproperties::set(&prop_name, string_value)?; + let parsed: bool = rsproperties::get(&prop_name)?; + assert_eq!(parsed, *expected, "Valid bool case failed for {name}"); + } + + // Test invalid cases (should fail to parse) + for (name, string_value) in invalid_cases.iter() { + let prop_name = format!("test.bool_invalid.{name}"); + rsproperties::set(&prop_name, string_value)?; + let result: Result = rsproperties::get(&prop_name); + assert!( + result.is_err(), + "Invalid bool case should fail for {name} ({string_value})" + ); + + // But get_parsed_with_default should return the default + let with_default: bool = rsproperties::get_or(&prop_name, true); + assert!( + with_default, + "get_parsed_with_default should return default for {name}" + ); + } + + Ok(()) +} + +/// Test type safety guarantees +#[tokio::test] +async fn test_type_safety() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.type_safety.value"; + + // Set a value that can be parsed as multiple types + rsproperties::set(prop_name, "42")?; + + // Should parse successfully as different numeric types + let as_i8: i8 = rsproperties::get(prop_name)?; + let as_i16: i16 = rsproperties::get(prop_name)?; + let as_i32: i32 = rsproperties::get(prop_name)?; + let as_i64: i64 = rsproperties::get(prop_name)?; + let as_u8: u8 = rsproperties::get(prop_name)?; + let as_u16: u16 = rsproperties::get(prop_name)?; + let as_u32: u32 = rsproperties::get(prop_name)?; + let as_u64: u64 = rsproperties::get(prop_name)?; + let as_f32: f32 = rsproperties::get(prop_name)?; + let as_f64: f64 = rsproperties::get(prop_name)?; + let as_string: String = rsproperties::get(prop_name)?; + + assert_eq!(as_i8, 42); + assert_eq!(as_i16, 42); + assert_eq!(as_i32, 42); + assert_eq!(as_i64, 42); + assert_eq!(as_u8, 42); + assert_eq!(as_u16, 42); + assert_eq!(as_u32, 42); + assert_eq!(as_u64, 42); + assert_eq!(as_f32, 42.0); + assert_eq!(as_f64, 42.0); + assert_eq!(as_string, "42"); + + Ok(()) +} + +/// Test API behavior with real-world Android property patterns +#[tokio::test] +async fn test_android_property_patterns() -> anyhow::Result<()> { + setup_test_env().await; + + let test_cases = [ + // SDK version + ("ro.build.version.sdk", "34", 34i32), + // Boolean properties (as integers) + ("ro.debuggable", "0", 0i32), + ("debug.enable_logs", "1", 1i32), + // Memory sizes (in MB) + ("dalvik.vm.heapsize", "512", 512i32), + ("dalvik.vm.heapgrowthlimit", "256", 256i32), + // Timeouts (in milliseconds) + ("debug.timeout.network", "30000", 30000i32), + ("persist.vendor.timeout", "5000", 5000i32), + // Percentage values + ("sys.battery.level", "85", 85i32), + ("vendor.display.brightness", "50", 50i32), + ]; + + for (prop_name, string_val, expected_int) in test_cases.iter() { + // Set as string (simulating property file loading) + rsproperties::set(prop_name, string_val)?; + + // Parse as integer (typical app usage) + let parsed_int: i32 = rsproperties::get(prop_name)?; + assert_eq!(parsed_int, *expected_int, "Failed for property {prop_name}"); + + // Get with default (safe app usage) + let with_default: i32 = rsproperties::get_or(prop_name, -1); + assert_eq!( + with_default, *expected_int, + "get_parsed_with_default failed for {prop_name}" + ); + + // Verify string representation is preserved + let as_string: String = rsproperties::get(prop_name)?; + assert_eq!( + as_string, *string_val, + "String representation changed for {prop_name}" + ); + } + + Ok(()) +} + +/// Benchmark-style test to ensure reasonable performance +#[tokio::test] +async fn test_performance_characteristics() -> anyhow::Result<()> { + setup_test_env().await; + + let num_operations = 100; // Reduced from 1000 to avoid property service limits + let start_time = std::time::Instant::now(); + + for i in 0..num_operations { + let prop_name = format!("test.perf_reduced.prop_{i}"); + + // Set operation + rsproperties::set(&prop_name, &i)?; + + // Get operation + let _retrieved: String = rsproperties::get(&prop_name)?; + + // Parse operation + let _parsed: i32 = rsproperties::get(&prop_name)?; + } + + let elapsed = start_time.elapsed(); + let ops_per_second = (num_operations as f64) / elapsed.as_secs_f64(); + + println!( + "Performance: {:.0} operations/second ({:.2} ms total)", + ops_per_second, + elapsed.as_millis() + ); + + // Ensure reasonable performance (should complete 100 ops in reasonable time) + assert!( + elapsed.as_secs() < 5, + "Performance test took too long: {elapsed:?}" + ); + + Ok(()) +} diff --git a/libs/rust/rsproperties-service/tests/error_handling_tests.rs b/libs/rust/rsproperties-service/tests/error_handling_tests.rs new file mode 100644 index 0000000..255ed70 --- /dev/null +++ b/libs/rust/rsproperties-service/tests/error_handling_tests.rs @@ -0,0 +1,507 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Error handling and edge case tests for new property APIs +//! +//! These tests focus on: +//! - Error conditions and error message validation +//! - Edge cases and boundary conditions +//! - Robustness and reliability +//! - API contract validation + +#[path = "common.rs"] +mod common; +use common::init_test; + +async fn setup_test_env() { + let _ = env_logger::builder().is_test(true).try_init(); + init_test().await; +} + +#[tokio::test] +async fn test_get_parsed_error_types() -> anyhow::Result<()> { + setup_test_env().await; + + // Test NotFound error + let non_existent_prop = "absolutely.does.not.exist"; + let result: Result = rsproperties::get(non_existent_prop); + assert!(result.is_err()); + let error_str = result.unwrap_err().to_string(); + assert!( + error_str.to_lowercase().contains("not found") + || error_str.to_lowercase().contains("notfound"), + "Expected NotFound error, got: {error_str}" + ); + + // Test Parse error + let parse_error_prop = "test.error.parse"; + rsproperties::set(parse_error_prop, "definitely_not_a_number")?; + let result: Result = rsproperties::get(parse_error_prop); + assert!(result.is_err()); + let error_str = result.unwrap_err().to_string(); + assert!( + error_str.to_lowercase().contains("parse") || error_str.to_lowercase().contains("failed"), + "Expected Parse error, got: {error_str}" + ); + + // Test empty string parsing + let empty_prop = "test.error.empty"; + rsproperties::set(empty_prop, "")?; + let result: Result = rsproperties::get(empty_prop); + assert!(result.is_err()); + + Ok(()) +} + +#[tokio::test] +async fn test_numeric_overflow_edge_cases() -> anyhow::Result<()> { + setup_test_env().await; + + // Test values that overflow smaller integer types + let overflow_cases = [ + ("i8_overflow", (i8::MAX as i32 + 1).to_string()), + ("i16_overflow", (i16::MAX as i32 + 1).to_string()), + ("u8_overflow", (u8::MAX as u32 + 1).to_string()), + ("u16_overflow", (u16::MAX as u32 + 1).to_string()), + ]; + + for (name, overflow_value) in overflow_cases.iter() { + let prop_name = format!("test.overflow.{name}"); + rsproperties::set(&prop_name, overflow_value)?; + + // Should parse successfully as larger types + let as_i32: i32 = rsproperties::get(&prop_name)?; + assert_eq!(as_i32.to_string(), *overflow_value); + + // But should fail for smaller types (depending on the specific case) + if name.contains("i8") { + let result: Result = rsproperties::get(&prop_name); + assert!(result.is_err(), "i8 should overflow for {overflow_value}"); + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_floating_point_special_values() -> anyhow::Result<()> { + setup_test_env().await; + + let special_values = [ + ("infinity", "inf"), + ("negative_infinity", "-inf"), + ("nan", "NaN"), + ("scientific_notation", "1.23e10"), + ("negative_scientific", "-4.56e-7"), + ]; + + for (name, special_value) in special_values.iter() { + let prop_name = format!("test.float_special.{name}"); + rsproperties::set(&prop_name, special_value)?; + + let result: Result = rsproperties::get(&prop_name); + + match *name { + "infinity" => { + if let Ok(val) = result { + assert!(val.is_infinite() && val.is_sign_positive()); + } + // Some systems might not support parsing "inf" + } + "negative_infinity" => { + if let Ok(val) = result { + assert!(val.is_infinite() && val.is_sign_negative()); + } + } + "nan" => { + if let Ok(val) = result { + assert!(val.is_nan()); + } + } + "scientific_notation" => { + let val: f64 = result?; + assert!((val - 1.23e10).abs() < 1e6); + } + "negative_scientific" => { + let val: f64 = result?; + assert!((val - (-4.56e-7)).abs() < 1e-10); + } + _ => {} + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_string_parsing_edge_cases() -> anyhow::Result<()> { + setup_test_env().await; + + let edge_cases = [ + ("empty_string", ""), + ("whitespace_only", " "), + ("tab_and_newline", "\t\n"), + ("leading_zeros", "000123"), + ("leading_plus", "+42"), + ("multiple_signs", "++42"), + ("decimal_only", "."), + ("decimal_no_digits", ".123"), + ("trailing_garbage", "42abc"), + ("embedded_space", "4 2"), + ]; + + for (name, test_value) in edge_cases.iter() { + let prop_name = format!("test.string_edge.{name}"); + rsproperties::set(&prop_name, test_value)?; + + // Try parsing as integer + let int_result: Result = rsproperties::get(&prop_name); + let is_error = int_result.is_err(); + + // Check if the result makes sense + match name { + &"leading_zeros" => { + let val = int_result.expect("leading_zeros should parse successfully"); + assert_eq!(val, 123); + } + &"leading_plus" => { + let val = int_result.expect("leading_plus should parse successfully"); + assert_eq!(val, 42); + } + &"empty_string" | &"whitespace_only" | &"tab_and_newline" | &"multiple_signs" + | &"decimal_only" | &"trailing_garbage" | &"embedded_space" => { + assert!(is_error, "Should fail to parse '{test_value}' as integer"); + } + &"decimal_no_digits" => { + // This should fail for integer parsing + assert!(is_error, "Should fail to parse '{test_value}' as integer"); + } + &"very_long_number" => { + // This might succeed or fail depending on the number size + // We just ensure it doesn't panic + } + _ => { + // For other cases, we just ensure no panic occurs + } + } + + // get_parsed_with_default should always succeed + let with_default: i32 = rsproperties::get_or(&prop_name, 999); + if is_error { + assert_eq!( + with_default, 999, + "get_parsed_with_default should return default for invalid input: '{test_value}'" + ); + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_property_name_edge_cases() -> anyhow::Result<()> { + setup_test_env().await; + + let name_cases = [ + ("very_long_name", "a".repeat(500)), + ("unicode_name", "测试.property.名称".to_string()), + ("special_chars_name", "test.@#$%.property".to_string()), + ("numbers_only", "123.456.789".to_string()), + ("single_char", "a".to_string()), + ("dots_only", "...".to_string()), + ("mixed_case", "Test.Property.Name".to_string()), + ]; + + for (test_name, prop_name) in name_cases.iter() { + let test_value = "test_value"; + + // Try setting the property + let set_result = rsproperties::set(prop_name, test_value); + + match set_result { + Ok(_) => { + // If setting succeeded, getting should work too + let retrieved: String = rsproperties::get(prop_name)?; + assert_eq!( + retrieved, test_value, + "Retrieved value doesn't match for property name case: {test_name}" + ); + } + Err(e) => { + // Some property names might be rejected by the system + println!("Property name '{prop_name}' was rejected: {e}"); + // This is acceptable behavior for invalid names + } + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_property_value_size_limits() -> anyhow::Result<()> { + setup_test_env().await; + + let size_cases = [ + ("small", "x".repeat(10)), + ("medium", "x".repeat(100)), + ("large", "x".repeat(1000)), + ("very_large", "x".repeat(10000)), + ("extremely_large", "x".repeat(100000)), + ]; + + for (test_name, test_value) in size_cases.iter() { + let prop_name = format!("test.size_limits.{test_name}"); + + let set_result = rsproperties::set(&prop_name, test_value); + + match set_result { + Ok(_) => { + let retrieved: String = rsproperties::get(&prop_name)?; + // The value might be truncated by the system + if retrieved.len() != test_value.len() { + println!( + "Value was truncated for {}: {} -> {} chars", + test_name, + test_value.len(), + retrieved.len() + ); + } + // Should at least preserve some of the value + assert!( + !retrieved.is_empty() || test_value.is_empty(), + "Value completely lost for {test_name}" + ); + } + Err(e) => { + println!("Large value rejected for {test_name}: {e}"); + // This is acceptable for very large values + } + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_concurrent_error_scenarios() -> anyhow::Result<()> { + setup_test_env().await; + + let num_tasks = 10; + + let handles: Vec<_> = (0..num_tasks) + .map(|task_id| { + tokio::spawn(async move { + // Each task tries various error scenarios + let base_name = format!("test.concurrent_errors.task_{task_id}"); + + // Try parsing non-existent property + let non_existent = format!("{base_name}.non_existent"); + let result: Result = rsproperties::get(&non_existent); + assert!(result.is_err()); + + // Set invalid value and try parsing + let invalid_prop = format!("{base_name}.invalid"); + rsproperties::set(&invalid_prop, "not_a_number")?; + let result: Result = rsproperties::get(&invalid_prop); + assert!(result.is_err()); + + // But get_parsed_with_default should work + let with_default: i32 = rsproperties::get_or(&invalid_prop, 42); + assert_eq!(with_default, 42); + + anyhow::Ok(()) + }) + }) + .collect(); + + for handle in handles { + handle.await??; + } + + Ok(()) +} + +#[tokio::test] +async fn test_api_contract_validation() -> anyhow::Result<()> { + setup_test_env().await; + + // Test that API contracts are maintained + + // Contract 1: get_parsed_with_default never fails + let test_cases = [ + ("non.existent.property", 42), + ("test.contract.invalid", 999), + ]; + + // Set up invalid value for second test case + rsproperties::set("test.contract.invalid", "invalid_number")?; + + for (prop_name, default_value) in test_cases.iter() { + let result: i32 = rsproperties::get_or(prop_name, *default_value); + assert_eq!( + result, *default_value, + "get_parsed_with_default should always return default for invalid cases" + ); + } + + // Contract 2: get() never panics, always returns a string + let problematic_names = [ + "non.existent.property", + "", // empty name + "test.non.empty.value", // Changed from test.empty.value since empty strings might not be supported + ]; + + rsproperties::set("test.non.empty.value", "some_value")?; + + for prop_name in problematic_names.iter() { + // For contract testing, we want to check that get() never panics + // even for non-existent properties. Use unwrap_or_default to handle errors gracefully + let result: String = rsproperties::get(prop_name).unwrap_or_default(); + // Should always return a string (ensures no panic occurs) + let _ = result.len(); // This accesses the result to ensure no panic + } + + // Contract 3: set operations with valid strings should not fail + let valid_operations = [ + ("test.contract.set1", "value1"), + ("test.contract.set2", "123"), + ("test.contract.set3", "true"), + ("test.contract.set4", "non_empty_value"), // Changed from empty string + ]; + + for (prop_name, prop_value) in valid_operations.iter() { + let result = rsproperties::set(prop_name, prop_value); + assert!( + result.is_ok(), + "Valid set operation should succeed: {prop_name}={prop_value}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_memory_usage_patterns() -> anyhow::Result<()> { + setup_test_env().await; + + // Test that repeated operations don't cause memory issues + let num_iterations = 100; + + for i in 0..num_iterations { + let prop_name = format!("test.memory.iteration_{i}"); + + // Create and destroy many property values + rsproperties::set(&prop_name, &i)?; + let _retrieved: String = rsproperties::get(&prop_name)?; + let _parsed: i32 = rsproperties::get(&prop_name)?; + + // Overwrite with different types + rsproperties::set(&prop_name, &(i as f64 * 1.5))?; + let _as_float: f64 = rsproperties::get(&prop_name)?; + + rsproperties::set(&prop_name, &format!("string_{i}"))?; + let _as_string: String = rsproperties::get(&prop_name)?; + } + + // If we get here without crashing, memory management is working + Ok(()) +} + +#[tokio::test] +async fn test_error_message_quality() -> anyhow::Result<()> { + setup_test_env().await; + + // Test that error messages are informative + + // Test 1: Non-existent property + let result: Result = rsproperties::get("non.existent.test.property"); + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + // Error should mention the property name + assert!( + error_msg.contains("non.existent.test.property") + || error_msg.to_lowercase().contains("not found") + || error_msg.to_lowercase().contains("notfound"), + "Error message should be informative: '{error_msg}'" + ); + + // Test 2: Parse error + rsproperties::set("test.error_msg.parse", "invalid_for_integer")?; + let result: Result = rsproperties::get("test.error_msg.parse"); + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + // Error should mention parsing failure and the value + assert!( + error_msg.to_lowercase().contains("parse") + || error_msg.to_lowercase().contains("invalid") + || error_msg.contains("invalid_for_integer"), + "Parse error message should be informative: '{error_msg}'" + ); + + Ok(()) +} + +#[tokio::test] +async fn test_type_conversion_boundaries() -> anyhow::Result<()> { + setup_test_env().await; + + // Test conversion at type boundaries + let boundary_tests = [ + // (test_name, value, should_succeed_as_i32, should_succeed_as_u32) + ("i32_max_positive", i32::MAX.to_string(), true, true), + ("negative_value", "-1".to_string(), true, false), // Should fail for u32 + ("small_positive", "100".to_string(), true, true), + ("u32_max_large", u32::MAX.to_string(), false, true), // Should fail for i32 + ]; + + for (test_name, value_str, should_succeed_signed, should_succeed_unsigned) in + boundary_tests.iter() + { + let prop_name = format!("test.boundaries.{test_name}"); + rsproperties::set(&prop_name, value_str)?; + + // Try parsing as signed + let signed_result: Result = rsproperties::get(&prop_name); + + // Try parsing as unsigned + let unsigned_result: Result = rsproperties::get(&prop_name); + + if *should_succeed_signed { + assert!( + signed_result.is_ok(), + "Should parse successfully as i32 for {test_name}: {value_str}" + ); + } else { + assert!( + signed_result.is_err(), + "Should fail to parse as i32 for {test_name}: {value_str}" + ); + } + + if *should_succeed_unsigned { + assert!( + unsigned_result.is_ok(), + "Should parse successfully as u32 for {test_name}: {value_str}" + ); + } else { + assert!( + unsigned_result.is_err(), + "Should fail to parse as u32 for {test_name}: {value_str}" + ); + } + + // Verify get_parsed_with_default behavior + let default_signed: i32 = rsproperties::get_or(&prop_name, -999); + let default_unsigned: u32 = rsproperties::get_or(&prop_name, 999); + + if signed_result.is_err() { + assert_eq!(default_signed, -999); + } + if unsigned_result.is_err() { + assert_eq!(default_unsigned, 999); + } + } + + Ok(()) +} diff --git a/libs/rust/rsproperties-service/tests/example_usage_tests.rs b/libs/rust/rsproperties-service/tests/example_usage_tests.rs new file mode 100644 index 0000000..4e4781f --- /dev/null +++ b/libs/rust/rsproperties-service/tests/example_usage_tests.rs @@ -0,0 +1,305 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Simple example tests demonstrating basic usage of rsproperties +//! +//! These tests serve as both verification and documentation +//! of the most common use cases. + +use rsproperties::{self, Result}; + +#[path = "common.rs"] +mod common; +use common::init_test; + +/// Basic usage example - demonstrates the most common patterns +#[tokio::test] +async fn example_basic_usage() -> Result<()> { + // Initialize the library with a test directory + init_test().await; + + // Reading a property that doesn't exist - use get_with_default + let sdk_version = rsproperties::get_or("ro.build.version.sdk", "unknown".to_string()); + println!("SDK Version: {sdk_version}"); + assert_eq!(sdk_version, "unknown"); // Should return default since property doesn't exist + + // Reading a property that might not exist - get returns String directly now + let model: String = rsproperties::get("ro.product.model").unwrap_or_default(); + if model.is_empty() { + println!("Product model not available"); + } else { + println!("Product Model: {model}"); + } + + Ok(()) +} + +#[tokio::test] +async fn example_setting_properties() -> Result<()> { + // Initialize for testing + init_test().await; + + // Set a simple property + rsproperties::set("my.app.version", "1.0.0")?; + + // Read it back + let version: String = rsproperties::get("my.app.version")?; + assert_eq!(version, "1.0.0"); + println!("App version: {version}"); + + // Update the property + rsproperties::set("my.app.version", "1.0.1")?; + let updated_version: String = rsproperties::get("my.app.version")?; + assert_eq!(updated_version, "1.0.1"); + println!("Updated app version: {updated_version}"); + + // Set multiple properties + let app_properties = vec![ + ("my.app.name", "RsProperties Example"), + ("my.app.author", "Rust Developer"), + ("my.app.debug", "true"), + ]; + + for (key, value) in &app_properties { + rsproperties::set(key, value)?; + } + + // Read them all back + for (key, expected_value) in &app_properties { + let actual_value: String = rsproperties::get(key)?; + assert_eq!(actual_value, *expected_value); + println!("{key} = {actual_value}"); + } + + Ok(()) +} + +/// Example showing error handling patterns +#[tokio::test] +async fn example_error_handling() { + init_test().await; + + // Safe way - always get a value, using default for missing properties + let timeout = rsproperties::get_or("network.timeout", "30".to_string()); + let timeout_seconds: u32 = timeout.parse().unwrap_or(30); + println!("Network timeout: {timeout_seconds} seconds"); + + // Check property value + let status: String = rsproperties::get("service.status").unwrap_or_default(); + if !status.is_empty() { + println!("Service status: {status}"); + // Process the status... + } else { + eprintln!("Could not get service status: property not found"); + // Handle the missing property appropriately... + } + + // Using Result in a function that can fail + fn get_required_config() -> Result { + let config: String = rsproperties::get("app.required.config")?; + if config.is_empty() { + return Err(rsproperties::Error::new_not_found( + "app.required.config".to_string(), + )); + } + Ok(config) + } + + match get_required_config() { + Ok(config) => println!("Config: {config}"), + Err(e) => println!("Configuration error: {e}"), + } +} + +/// Example showing different property patterns used in Android +#[tokio::test] +async fn example_android_property_patterns() { + init_test().await; + + // Common Android property patterns and their typical usage + let android_properties = vec![ + // Read-only build properties + ("ro.build.version.release", "Release version"), + ("ro.build.version.sdk", "SDK API level"), + ("ro.product.manufacturer", "Device manufacturer"), + ("ro.product.model", "Device model"), + ("ro.product.name", "Product name"), + ("ro.hardware", "Hardware platform"), + // System properties + ("sys.boot_completed", "Boot completion status"), + ("sys.usb.state", "USB connection state"), + // Persistent properties (survive reboots) + ("persist.sys.timezone", "System timezone"), + ("persist.sys.locale", "System locale"), + // Service properties + ("service.adb.tcp.port", "ADB TCP port"), + // Dalvik VM properties + ("dalvik.vm.heapsize", "VM heap size"), + // Network properties + ("net.hostname", "Network hostname"), + ]; + + for (prop, description) in android_properties { + let value = rsproperties::get_or(prop, "not_set".to_string()); + println!("{description}: {prop} = {value}"); + + // Demonstrate type conversion for numeric properties + if prop.contains("sdk") || prop.contains("port") { + if let Ok(numeric_value) = value.parse::() { + println!(" Parsed as number: {numeric_value}"); + } + } + + // Demonstrate boolean conversion + if prop.contains("completed") || prop.contains("debug") { + let bool_value = value == "1" || value.to_lowercase() == "true"; + println!(" Parsed as boolean: {bool_value}"); + } + } +} + +#[tokio::test] +async fn example_configuration_management() -> Result<()> { + init_test().await; + + // Example: Managing application configuration through properties + + // Set up default configuration + let default_config = vec![ + ("app.log.level", "info"), + ("app.max.connections", "100"), + ("app.timeout.seconds", "30"), + ("app.feature.experimental", "false"), + ("app.cache.size.mb", "256"), + ]; + + println!("Setting up default configuration..."); + for (key, value) in &default_config { + rsproperties::set(key, value)?; + println!(" {key} = {value}"); + } + + // Simulate configuration updates + println!("\nUpdating configuration..."); + rsproperties::set("app.log.level", "debug")?; + rsproperties::set("app.feature.experimental", "true")?; + + // Read and use configuration + println!("\nReading current configuration:"); + + let log_level: String = rsproperties::get("app.log.level")?; + println!("Log level: {log_level}"); + + let max_connections: i32 = rsproperties::get("app.max.connections").unwrap_or(50); + println!("Max connections: {max_connections}"); + + let timeout: u64 = rsproperties::get("app.timeout.seconds").unwrap_or(10); + println!("Timeout: {timeout} seconds"); + + let experimental_enabled = + rsproperties::get_or("app.feature.experimental", "".to_owned()).to_lowercase() == "true"; + println!("Experimental features: {experimental_enabled}"); + + let cache_size: u32 = rsproperties::get("app.cache.size.mb").unwrap_or(128); + println!("Cache size: {cache_size} MB"); + + // Demonstrate conditional logic based on properties + if experimental_enabled { + println!("🧪 Experimental features are enabled!"); + rsproperties::set("app.experimental.new_algorithm", "active")?; + } + + if log_level == "debug" { + println!("🔍 Debug logging is enabled"); + rsproperties::set("app.debug.verbose", "true")?; + } + + Ok(()) +} + +/// Example showing property watching patterns (conceptual) +#[tokio::test] +async fn example_property_monitoring() { + init_test().await; + + // This demonstrates how you might monitor properties in a real application + // Note: Actual watching would require the wait functionality from SystemProperties + + println!("Property monitoring example:"); + + let monitored_properties = vec![ + "system.state", + "network.connected", + "battery.level", + "app.should_exit", + ]; + + for prop in &monitored_properties { + let current_value = rsproperties::get_or(prop, "unknown".to_string()); + println!("Currently monitoring {prop}: {current_value}"); + } + + // In a real application, you might: + // 1. Set up a monitoring thread + // 2. Use system_properties().wait() to wait for changes + // 3. React to property changes + + println!("In a real app, you would set up listeners for these properties..."); +} + +/// Example demonstrating best practices +#[tokio::test] +async fn example_best_practices() { + init_test().await; + + // ✅ Good: Use meaningful property names with clear hierarchy + { + println!("Setting up application properties..."); + rsproperties::set("com.myapp.feature.cache.enabled", "true").unwrap(); + rsproperties::set("com.myapp.network.retry.count", "3").unwrap(); + rsproperties::set("com.myapp.ui.theme", "dark").unwrap(); + println!("Properties set successfully."); + } + + // ✅ Good: Always provide sensible defaults + let cache_enabled = + rsproperties::get_or("com.myapp.feature.cache.enabled", "false".to_string()); + let retry_count: u32 = rsproperties::get_or("com.myapp.network.retry.count", "1".to_string()) + .parse() + .unwrap_or(1); + + println!("Cache enabled: {cache_enabled}"); + println!("Retry count: {retry_count}"); + + // ✅ Good: Handle missing properties appropriately + let setting: String = rsproperties::get("com.myapp.critical.setting").unwrap_or_default(); + if !setting.is_empty() { + println!("Critical setting: {setting}"); + // Proceed with the setting + } else { + println!("Critical setting not found, using safe defaults"); + // Use safe defaults or fail safely + } + + // ✅ Good: Validate property values + let theme = rsproperties::get_or("com.myapp.ui.theme", "light".to_string()); + let valid_theme = match theme.as_str() { + "light" | "dark" | "auto" => theme, + _ => { + println!("Invalid theme '{theme}', using 'light'"); + "light".to_string() + } + }; + println!("Using theme: {valid_theme}"); + + // ✅ Good: Use constants for property names to avoid typos + const FEATURE_FLAG_ANALYTICS: &str = "com.myapp.feature.analytics.enabled"; + const FEATURE_FLAG_TELEMETRY: &str = "com.myapp.feature.telemetry.enabled"; + + let analytics_enabled = + rsproperties::get_or(FEATURE_FLAG_ANALYTICS, "false".to_string()) == "true"; + let telemetry_enabled = + rsproperties::get_or(FEATURE_FLAG_TELEMETRY, "false".to_string()) == "true"; + + println!("Analytics: {analytics_enabled}, Telemetry: {telemetry_enabled}"); +} diff --git a/libs/rust/rsproperties-service/tests/final_comprehensive_tests.rs b/libs/rust/rsproperties-service/tests/final_comprehensive_tests.rs new file mode 100644 index 0000000..d37e714 --- /dev/null +++ b/libs/rust/rsproperties-service/tests/final_comprehensive_tests.rs @@ -0,0 +1,493 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Final comprehensive test suite for rsproperties +//! +//! This test suite provides complete coverage of the rsproperties public API +//! including constants validation, property operations, error handling, +//! thread safety, and performance testing. + +extern crate rsproperties; + +use rsproperties::{PROP_DIRNAME, PROP_VALUE_MAX}; + +#[path = "common.rs"] +mod common; +use common::init_test; + +#[test] +fn test_api_constants() { + // Verify Android system property constants are correct + assert_eq!( + PROP_VALUE_MAX, 92, + "PROP_VALUE_MAX should match Android spec" + ); + assert_eq!( + PROP_DIRNAME, "/dev/__properties__", + "PROP_DIRNAME should match Android default" + ); + + println!("✓ API constants validation passed"); + println!(" PROP_VALUE_MAX = {PROP_VALUE_MAX}"); + println!(" PROP_DIRNAME = '{PROP_DIRNAME}'"); +} + +#[tokio::test] +async fn test_get_with_default_comprehensive() { + init_test().await; + + // Test cases for get_with_default function + let test_cases = [ + ("test.simple", "default", "should handle simple case"), + ("test.empty.default", "", "should handle empty default"), + ( + "test.spaces", + "default with spaces", + "should handle defaults with spaces", + ), + ( + "test.special.chars", + "!@#$%^&*()", + "should handle special characters", + ), + ( + "test.unicode", + "üñíçødé", + "should handle unicode characters", + ), + ( + "test.long.property.name.with.many.dots", + "default", + "should handle long property names", + ), + ( + "test.numbers.123", + "456", + "should handle numbers in names and values", + ), + ]; + + for (property, default, description) in &test_cases { + let result = rsproperties::get_or(property, default.to_string()); + assert_eq!(result, *default, "{description}"); + } + + println!( + "✓ get_with_default comprehensive tests passed ({} cases)", + test_cases.len() + ); +} + +#[tokio::test] +async fn test_get_nonexistent_properties() { + init_test().await; + + // Test that getting non-existent properties returns errors + let nonexistent_properties = [ + "definitely.not.there", + "fake.property.12345", + "test.nonexistent.with.very.long.name.that.should.definitely.not.exist", + "x", + "test", + "this.property.does.not.exist", + ]; + + for property in &nonexistent_properties { + let result = rsproperties::get::(property); + assert!(result.is_err(), "Property '{property}' should not exist"); + } + + println!( + "✓ get non-existent properties test passed ({} properties tested)", + nonexistent_properties.len() + ); +} + +#[tokio::test] +async fn test_dirname_functionality() { + init_test().await; + + let dirname = rsproperties::properties_dir(); + let dirname_str = dirname.to_string_lossy(); + + // Verify dirname is not empty and looks like a path + assert!(!dirname_str.is_empty(), "dirname should not be empty"); + assert!( + dirname_str.contains("properties") || dirname_str.starts_with("/"), + "dirname should be a valid path, got: '{dirname_str}'" + ); + + println!("✓ dirname functionality test passed"); + println!(" Current dirname: '{dirname_str}'"); +} + +#[tokio::test] +async fn test_property_name_validation() { + init_test().await; + + // Test various property name formats + let valid_format_names = [ + "simple", + "test.property", + "ro.build.version.sdk", + "sys.boot_completed", + "persist.sys.timezone", + "a.b.c.d.e.f.g.h.i.j", + "property123", + "test_underscore", + "MixedCase.Property", + ]; + + // These names are valid format-wise, they may or may not exist + for name in &valid_format_names { + let _result: String = rsproperties::get(name).unwrap_or_default(); + let _default_result = rsproperties::get_or(name, "default".to_string()); + // We don't assert success/failure since properties may or may not exist + } + + // Test potentially problematic property names + let edge_case_names = [ + "", // empty name + ".", // just dot + "..", // double dot + "name.", // ending with dot + ".name", // starting with dot + ]; + + for name in &edge_case_names { + let _result: String = rsproperties::get(name).unwrap_or_default(); + // Don't assert specific behavior as implementation may vary + } + + println!("✓ Property name validation test completed"); + println!(" Tested {} valid format names", valid_format_names.len()); + println!(" Tested {} edge case names", edge_case_names.len()); +} + +#[tokio::test] +async fn test_property_value_length_limits() { + // Test maximum value length constant + let max_length_value = "x".repeat(PROP_VALUE_MAX); + assert_eq!(max_length_value.len(), PROP_VALUE_MAX); + + let too_long_value = "x".repeat(PROP_VALUE_MAX + 1); + assert_eq!(too_long_value.len(), PROP_VALUE_MAX + 1); + + init_test().await; + + // Test with get_with_default (should work regardless of length) + let result1 = rsproperties::get_or("test.max.length", max_length_value.to_string()); + assert_eq!(result1, max_length_value); + + let result2 = rsproperties::get_or("test.too.long", too_long_value.to_string()); + assert_eq!(result2, too_long_value); + + println!("✓ Property value length limits test passed"); + println!(" PROP_VALUE_MAX = {PROP_VALUE_MAX}"); + println!( + " Tested values of length {} and {}", + max_length_value.len(), + too_long_value.len() + ); +} + +#[tokio::test] +async fn test_thread_safety() { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + use std::thread; + + init_test().await; + + let success_count = Arc::new(AtomicUsize::new(0)); + let mut handles = vec![]; + + // Spawn multiple threads that perform property operations concurrently + for thread_id in 0..10 { + let success_count_clone = Arc::clone(&success_count); + + let handle = thread::spawn(move || { + // Each thread performs multiple operations + for op_id in 0..5 { + let property_name = format!("test.thread.{thread_id}.{op_id}"); + + // Test get_with_default (should always succeed) + let result = rsproperties::get_or(&property_name, "default".to_string()); + if result == "default" { + success_count_clone.fetch_add(1, Ordering::SeqCst); + } + + // Test get (will likely fail but shouldn't crash) + let _result: String = rsproperties::get(&property_name).unwrap_or_default(); + success_count_clone.fetch_add(1, Ordering::SeqCst); + + // Test dirname (should always work) + let _dirname = rsproperties::properties_dir(); + success_count_clone.fetch_add(1, Ordering::SeqCst); + } + }); + + handles.push(handle); + } + + // Wait for all threads to complete + for handle in handles { + handle.join().expect("Thread should complete successfully"); + } + + let final_count = success_count.load(Ordering::SeqCst); + let expected_count = 10 * 5 * 3; // 10 threads × 5 operations × 3 calls per operation + assert_eq!( + final_count, expected_count, + "All thread operations should complete" + ); + + println!("✓ Thread safety test passed"); + println!( + " {} threads × 5 operations × 3 calls = {} total operations", + 10, expected_count + ); +} + +#[tokio::test] +async fn test_error_handling() { + init_test().await; + + // Test various error conditions + + // Very long property name + let long_name = "very.long.property.name.".repeat(50); + let _result: String = rsproperties::get(&long_name).unwrap_or_default(); + // May or may not fail depending on implementation limits + + // Empty property name + let _result: String = rsproperties::get("").unwrap_or_default(); + // Behavior may vary + + // Property name with only dots + let _result: String = rsproperties::get("...").unwrap_or_default(); + // Behavior may vary + + println!("✓ Error handling test completed"); + println!(" Tested various edge cases for error conditions"); +} + +#[tokio::test] +async fn test_performance_basic() { + use std::time::Instant; + + init_test().await; + + // Test performance of get_with_default + let start = Instant::now(); + let iterations = 1000; + + for i in 0..iterations { + let property_name = format!("test.perf.{i}"); + let _result = rsproperties::get_or(&property_name, "default".to_string()); + } + + let elapsed = start.elapsed(); + let ops_per_sec = iterations as f64 / elapsed.as_secs_f64(); + + println!("✓ Performance test completed"); + println!(" {iterations} operations in {elapsed:?}"); + println!(" {ops_per_sec:.0} operations per second"); + + // Performance should be reasonable (at least 1000 ops/sec) + assert!( + ops_per_sec > 1000.0, + "Performance should be at least 1000 ops/sec, got {ops_per_sec:.0}" + ); +} + +// Tests that require the builder feature +mod builder_tests { + use super::*; + + #[tokio::test] + async fn test_set_property_basic() { + init_test().await; + + let result = rsproperties::set("test.basic.set", "test_value"); + + match result { + Ok(_) => { + println!("✓ Property set successfully"); + + // Try to read it back + let value: String = rsproperties::get("test.basic.set").unwrap_or_default(); + assert_eq!(value, "test_value"); + println!("✓ Property read back successfully: '{value}'"); + } + Err(e) => { + println!("⚠ Property set failed (expected without property service): {e}"); + // This is expected when property service is not running + } + } + } + + #[tokio::test] + async fn test_set_property_various_values() { + init_test().await; + + let test_cases = [ + ("test.empty.value", ""), + ("test.simple.value", "simple"), + ("test.numeric.value", "12345"), + ("test.special.chars", "!@#$%^&*()"), + ("test.spaces.value", "value with spaces"), + ("test.unicode.value", "üñíçødé tëxt"), + ]; + + for (property, value) in &test_cases { + match rsproperties::set(property, value) { + Ok(_) => println!("✓ Set property '{property}' = '{value}'"), + Err(e) => println!("⚠ Failed to set property '{property}': {e}"), + } + } + + println!("✓ Set property various values test completed"); + } + + #[tokio::test] + async fn test_set_property_length_limits() { + init_test().await; + + // Test setting property with maximum allowed length + let max_value = "x".repeat(PROP_VALUE_MAX); + let result = rsproperties::set("test.max.length.set", &max_value); + + match result { + Ok(_) => println!("✓ Successfully set property with max length ({PROP_VALUE_MAX})"), + Err(e) => println!("⚠ Failed to set max length property: {e}"), + } + + // Test setting property with value that exceeds maximum length + let too_long_value = "x".repeat(PROP_VALUE_MAX + 1); + let result = rsproperties::set("test.too.long.set", &too_long_value); + + // This should typically fail, but behavior may vary + match result { + Ok(_) => println!("⚠ Unexpectedly succeeded setting overlong property"), + Err(_) => println!( + "✓ Correctly rejected property value that is too long ({})", + too_long_value.len() + ), + } + + println!("✓ Set property length limits test completed"); + } + + #[tokio::test] + async fn test_property_update() { + init_test().await; + + let property_name = "test.update.property"; + + // Set initial value + match rsproperties::set(property_name, "initial_value") { + Ok(_) => { + println!("✓ Set initial property value"); + + // Update the value + match rsproperties::set(property_name, "updated_value") { + Ok(_) => { + println!("✓ Updated property value"); + + // Verify the update + let value: String = rsproperties::get(property_name).unwrap_or_default(); + assert_eq!(value, "updated_value"); + println!("✓ Property update verified: '{value}'"); + } + Err(e) => println!("⚠ Property update failed: {e}"), + } + } + Err(e) => println!("⚠ Initial property set failed: {e}"), + } + + println!("✓ Property update test completed"); + } + + #[tokio::test] + async fn test_concurrent_property_sets() { + use std::thread; + + init_test().await; + + let mut handles = vec![]; + + // Spawn multiple threads that try to set properties + for thread_id in 0..5 { + let handle = thread::spawn(move || { + let property_name = format!("test.concurrent.set.{thread_id}"); + let property_value = format!("thread_{thread_id}_value"); + + match rsproperties::set(&property_name, &property_value) { + Ok(_) => { + println!( + "Thread {thread_id}: Set property '{property_name}' = '{property_value}'" + ); + + // Try to read it back + let value: String = rsproperties::get(&property_name).unwrap_or_default(); + println!("Thread {thread_id}: Read back value: '{value}'"); + if value == property_value { + println!("Thread {thread_id}: ✓ Value matches"); + } else { + println!("Thread {thread_id}: ⚠ Value mismatch"); + } + } + Err(e) => println!("Thread {thread_id}: ⚠ Set failed: {e}"), + } + }); + + handles.push(handle); + } + + // Wait for all threads + for handle in handles { + handle.join().expect("Thread should complete"); + } + + println!("✓ Concurrent property sets test completed"); + } +} + +#[tokio::test] +async fn test_integration_comprehensive() { + init_test().await; + + // Comprehensive integration test combining multiple operations + + // Test constants + assert_eq!(PROP_VALUE_MAX, 92); + assert_eq!(PROP_DIRNAME, "/dev/__properties__"); + + // Test dirname + let dirname = rsproperties::properties_dir(); + assert!(!dirname.to_string_lossy().is_empty()); + + // Test multiple get_with_default calls + let test_properties = [ + "integration.test.1", + "integration.test.2", + "integration.test.3", + ]; + + for (i, property) in test_properties.iter().enumerate() { + let default_value = format!("default_{i}"); + let result = rsproperties::get_or(property, default_value.to_string()); + assert_eq!(result, default_value); + } + + // Test error conditions + for property in &test_properties { + let result = rsproperties::get::(property); + assert!(result.is_err()); + } + + println!("✓ Comprehensive integration test passed"); + println!(" Tested constants, dirname, get_with_default, and get functions"); + println!(" All components working together correctly"); +} diff --git a/libs/rust/rsproperties-service/tests/integration_tests.rs b/libs/rust/rsproperties-service/tests/integration_tests.rs new file mode 100644 index 0000000..db2257b --- /dev/null +++ b/libs/rust/rsproperties-service/tests/integration_tests.rs @@ -0,0 +1,291 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Integration tests for rsproperties public API +//! +//! These tests verify the core functionality of the public API including: +//! - Property initialization +//! - Getting properties with default values +//! - Getting properties without defaults +//! - Setting properties (when builder feature is enabled) +//! - Error handling + +#[path = "common.rs"] +mod common; +use common::init_test; + +async fn setup_test_env() { + let _ = env_logger::builder().is_test(true).try_init(); + init_test().await; +} + +#[tokio::test] +async fn test_get_with_default_nonexistent_property() { + setup_test_env().await; + + let prop_name = "nonexistent.test.property"; + let default_value = "default_test_value"; + + let result = rsproperties::get_or(prop_name, default_value.to_string()); + assert_eq!(result, default_value); +} + +#[tokio::test] +async fn test_get_with_default_empty_default() { + setup_test_env().await; + + let prop_name = "another.nonexistent.property"; + let default_value = ""; + + let result = rsproperties::get_or(prop_name, default_value.to_string()); + assert_eq!(result, default_value); +} + +#[tokio::test] +async fn test_get_nonexistent_property() { + setup_test_env().await; + + let prop_name = "definitely.does.not.exist"; + let result: String = rsproperties::get(prop_name).unwrap_or_default(); + + // Should return empty string for non-existent property + assert!(result.is_empty()); +} + +mod builder_tests { + use super::*; + + #[tokio::test] + async fn test_set_and_get_property() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.set.property"; + let prop_value = "test_value_123"; + + // Set the property + rsproperties::set(prop_name, prop_value)?; + + // Get the property back + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, prop_value); + + // Also test get_with_default + let retrieved_with_default = rsproperties::get_or(prop_name, "fallback".to_string()); + assert_eq!(retrieved_with_default, prop_value); + + Ok(()) + } + + #[tokio::test] + async fn test_set_property_with_special_characters() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.special.chars"; + let prop_value = "value with spaces and symbols: !@#$%^&*()"; + + rsproperties::set(prop_name, prop_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, prop_value); + + Ok(()) + } + + #[tokio::test] + async fn test_update_existing_property() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.update.property"; + let initial_value = "initial_value"; + let updated_value = "updated_value"; + + // Set initial value + rsproperties::set(prop_name, initial_value)?; + let retrieved: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved, initial_value); + + // Update the value + rsproperties::set(prop_name, updated_value)?; + let retrieved: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved, updated_value); + + Ok(()) + } + + #[tokio::test] + async fn test_set_invalid_property_name() { + setup_test_env().await; + + // Test with empty property name + let result = rsproperties::set("", "value"); + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_multiple_properties() -> anyhow::Result<()> { + setup_test_env().await; + + let properties = vec![ + ("test.prop.one", "value1"), + ("test.prop.two", "value2"), + ("test.prop.three", "value3"), + ("test.prop.four", "value4"), + ]; + + // Set all properties + for (name, value) in &properties { + rsproperties::set(name, value)?; + } + + // Verify all properties + for (name, expected_value) in &properties { + let retrieved_value: String = rsproperties::get(name)?; + assert_eq!(retrieved_value, *expected_value); + } + + Ok(()) + } +} + +mod linux_specific_tests { + use super::*; + + #[tokio::test] + async fn test_property_persistence() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "persist.test.property"; + let prop_value = "persistent_value"; + + // Set a property + rsproperties::set(prop_name, prop_value)?; + + // Verify it's set + let retrieved: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved, prop_value); + + Ok(()) + } +} + +/// Test error handling and edge cases +mod error_handling_tests { + use super::*; + + #[tokio::test] + async fn test_get_with_very_long_property_name() { + setup_test_env().await; + + // Create a very long property name + let long_name = "a".repeat(1000); + let default_value = "default"; + + let result = rsproperties::get_or(&long_name, default_value.to_string()); + // Should return default value when property doesn't exist + assert_eq!(result, default_value); + } + + #[tokio::test] + async fn test_set_property_with_max_value_length() { + setup_test_env().await; + + let prop_name = "test.max.value"; + // Create a value close to PROP_VALUE_MAX + let long_value = "x".repeat(rsproperties::PROP_VALUE_MAX - 10); + + let result = rsproperties::set(prop_name, &long_value); + // This should succeed since it's within limits + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_set_property_exceeding_max_value_length() { + setup_test_env().await; + + let prop_name = "test.exceeding.max"; + // Create a value that exceeds PROP_VALUE_MAX + let too_long_value = "x".repeat(rsproperties::PROP_VALUE_MAX + 10); + + let result = rsproperties::set(prop_name, &too_long_value); + // This should fail or truncate the value + // The exact behavior depends on implementation + println!("Result for too long value: {result:?}"); + } +} + +/// Test concurrent access patterns +mod concurrency_tests { + use super::*; + use std::thread; + use std::time::Duration; + + #[tokio::test] + async fn test_concurrent_property_access() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.concurrent.property"; + let prop_value = "concurrent_value"; + + // Set initial property + rsproperties::set(prop_name, prop_value)?; + + let handles: Vec<_> = (0..5) + .map(|i| { + let name = prop_name.to_string(); + let expected = prop_value.to_string(); + + thread::spawn(move || { + for _ in 0..10 { + let value: String = rsproperties::get(&name).unwrap_or_default(); + assert_eq!(value, expected); + thread::sleep(Duration::from_millis(1)); + } + println!("Thread {i} completed"); + }) + }) + .collect(); + + for handle in handles { + handle.join().unwrap(); + } + + Ok(()) + } + + #[tokio::test] + async fn test_concurrent_property_updates() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.concurrent.updates"; + + // Set initial property + rsproperties::set(prop_name, "initial")?; + + let handles: Vec<_> = (0..3) + .map(|i| { + let name = prop_name.to_string(); + + thread::spawn(move || { + let value = format!("thread_{i}_value"); + rsproperties::set(&name, &value).unwrap(); + // thread::sleep(Duration::from_millis(10)); + + // Verify we can read some value back + let retrieved: String = rsproperties::get(&name).unwrap_or_default(); + assert!(!retrieved.is_empty()); + + println!("Thread {i} set value: {value}, got: {retrieved}"); + }) + }) + .collect(); + + for handle in handles { + handle.join().unwrap(); + } + + // Final verification + let final_value: String = rsproperties::get(prop_name).unwrap_or_default(); + assert!(!final_value.is_empty()); + + Ok(()) + } +} diff --git a/libs/rust/rsproperties-service/tests/new_api_showcase.rs b/libs/rust/rsproperties-service/tests/new_api_showcase.rs new file mode 100644 index 0000000..03a148c --- /dev/null +++ b/libs/rust/rsproperties-service/tests/new_api_showcase.rs @@ -0,0 +1,407 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Summary tests for newly added property APIs +//! +//! This test file provides a comprehensive overview of the new APIs: +//! - get_parsed_with_default() - Parse property values with fallback +//! - get_parsed() - Parse property values with error handling +//! - set() - Set property with string value +//! - set() - Set property with any Display type +//! +//! These tests demonstrate real-world usage patterns and serve as examples. + +#[path = "common.rs"] +mod common; +use common::init_test; + +async fn setup_test_env() { + let _ = env_logger::builder().is_test(true).try_init(); + init_test().await; +} + +/// Demonstrate the complete workflow for the new parsed property APIs +#[tokio::test] +async fn test_new_api_showcase() -> anyhow::Result<()> { + setup_test_env().await; + + // ================================= + // Part 1: Setting Properties + // ================================= + + // Set string property using set() + rsproperties::set("app.config.name", "MyApplication")?; + + // Set numeric properties using set() with different types + rsproperties::set("app.config.version_code", &123i32)?; + rsproperties::set("app.config.version_minor", &4u8)?; + rsproperties::set("app.config.memory_limit", &512.5f64)?; + rsproperties::set("app.config.debug_enabled", &true)?; + + // Set using string literals (also uses set()) + rsproperties::set("app.config.api_timeout", "30000")?; + + // Set a floating point value + rsproperties::set("app.config.ratio", &2.5f64)?; + + // ================================= + // Part 2: Getting and Parsing Properties + // ================================= + + // Get properties as strings (traditional approach) + let app_name: String = rsproperties::get("app.config.name")?; + assert_eq!(app_name, "MyApplication"); + + // Parse properties to specific types using get_parsed() + let version_code: i32 = rsproperties::get("app.config.version_code")?; + assert_eq!(version_code, 123); + + let version_minor: u8 = rsproperties::get("app.config.version_minor")?; + assert_eq!(version_minor, 4); + + let memory_limit: f64 = rsproperties::get("app.config.memory_limit")?; + assert!((memory_limit - 512.5).abs() < f64::EPSILON); + + let debug_enabled: bool = rsproperties::get("app.config.debug_enabled")?; + assert!(debug_enabled); + + // Parse string that was set as string + let api_timeout: u32 = rsproperties::get("app.config.api_timeout")?; + assert_eq!(api_timeout, 30000); + + // ================================= + // Part 3: Safe Parsing with Defaults + // ================================= + + // Get with defaults for existing properties + let existing_timeout: u32 = rsproperties::get_or("app.config.api_timeout", 5000); + assert_eq!(existing_timeout, 30000); // Should return actual value, not default + + // Get with defaults for non-existent properties + let missing_prop: i32 = rsproperties::get_or("app.config.nonexistent", 42); + assert_eq!(missing_prop, 42); // Should return default + + // Get with defaults for properties with invalid format + rsproperties::set("app.config.invalid_number", "not_a_number")?; + let invalid_parsed: i32 = rsproperties::get_or("app.config.invalid_number", 999); + assert_eq!(invalid_parsed, 999); // Should return default when parsing fails + + // ================================= + // Part 4: Error Handling + // ================================= + + // Attempting to parse non-existent property should fail + let missing_result: Result = rsproperties::get("app.config.does_not_exist"); + assert!(missing_result.is_err()); + + // Attempting to parse invalid format should fail + let invalid_result: Result = rsproperties::get("app.config.invalid_number"); + assert!(invalid_result.is_err()); + + println!("✅ All new API functionality demonstrated successfully!"); + + Ok(()) +} + +/// Demonstrate Android-style property usage patterns +#[tokio::test] +async fn test_android_style_usage() -> anyhow::Result<()> { + setup_test_env().await; + + // Simulate loading properties from build.prop files + rsproperties::set("ro.build.version.sdk", "34")?; + rsproperties::set("ro.debuggable", "0")?; + rsproperties::set("dalvik.vm.heapsize", "512m")?; + rsproperties::set("debug.my_app.log_level", "verbose")?; + + // Application code reading these properties + + // SDK version as integer + let sdk_version: i32 = rsproperties::get_or("ro.build.version.sdk", 28); + assert_eq!(sdk_version, 34); + + // Debug flag as boolean (0/1 pattern) + let is_debuggable: i32 = rsproperties::get_or("ro.debuggable", 0); + let debug_enabled = is_debuggable != 0; + assert!(!debug_enabled); + + // Memory setting parsing (would need additional logic for "m" suffix in real usage) + // For this demo, we'll set a numeric value + rsproperties::set("dalvik.vm.heapsize.mb", "512")?; + let heap_size: u32 = rsproperties::get_or("dalvik.vm.heapsize.mb", 256); + assert_eq!(heap_size, 512); + + // String property with fallback + let log_level = rsproperties::get_or("debug.my_app.log_level", "info".to_string()); + assert_eq!(log_level, "verbose"); + + // Non-existent property with fallback + let unknown_feature: i32 = rsproperties::get_or("vendor.unknown.feature", 0); + assert_eq!(unknown_feature, 0); + + println!("✅ Android-style property patterns work correctly!"); + + Ok(()) +} + +/// Demonstrate type conversion capabilities +#[tokio::test] +async fn test_type_conversion_showcase() -> anyhow::Result<()> { + setup_test_env().await; + + // Set a numeric value that can be interpreted as different types + rsproperties::set("config.numeric_value", "42")?; + + // Parse as different integer types + let as_i8: i8 = rsproperties::get("config.numeric_value")?; + let as_i16: i16 = rsproperties::get("config.numeric_value")?; + let as_i32: i32 = rsproperties::get("config.numeric_value")?; + let as_i64: i64 = rsproperties::get("config.numeric_value")?; + let as_u32: u32 = rsproperties::get("config.numeric_value")?; + let as_u64: u64 = rsproperties::get("config.numeric_value")?; + + assert_eq!(as_i8, 42); + assert_eq!(as_i16, 42); + assert_eq!(as_i32, 42); + assert_eq!(as_i64, 42); + assert_eq!(as_u32, 42); + assert_eq!(as_u64, 42); + + // Parse as floating point + let as_f32: f32 = rsproperties::get("config.numeric_value")?; + let as_f64: f64 = rsproperties::get("config.numeric_value")?; + + assert_eq!(as_f32, 42.0); + assert_eq!(as_f64, 42.0); + + // Parse as string + let as_string: String = rsproperties::get("config.numeric_value")?; + assert_eq!(as_string, "42"); + + // Set floating point and parse back + rsproperties::set("config.pi", &std::f64::consts::PI)?; + let pi_back: f64 = rsproperties::get("config.pi")?; + assert!((pi_back - std::f64::consts::PI).abs() < 1e-10); + + // Set boolean values + rsproperties::set("config.feature_flag", &true)?; + let flag_value: String = rsproperties::get("config.feature_flag")?; + assert_eq!(flag_value, "true"); + + // Note: Parsing "true"/"false" strings as bool works with Rust's FromStr + rsproperties::set("config.bool_string", "false")?; + let bool_parsed: bool = rsproperties::get("config.bool_string")?; + assert!(!bool_parsed); + + println!("✅ Type conversion showcase completed successfully!"); + + Ok(()) +} + +/// Demonstrate error handling and safe programming patterns +#[tokio::test] +async fn test_safe_programming_patterns() -> anyhow::Result<()> { + setup_test_env().await; + + // Pattern 1: Safe parsing with defaults (never fails) + let safe_timeout = rsproperties::get_or("config.timeout", 5000u32); + assert_eq!(safe_timeout, 5000); // Default because property doesn't exist + + // Pattern 2: Explicit error handling + match rsproperties::get::("config.nonexistent") { + Ok(_value) => panic!("Should not succeed for non-existent property"), + Err(_) => println!("✅ Correctly handled missing property error"), + } + + // Pattern 3: Validation after setting + rsproperties::set("config.validation_test", "123")?; + let validated: Result = rsproperties::get("config.validation_test"); + match validated { + Ok(val) => { + assert_eq!(val, 123); + println!("✅ Property validation passed: {val}"); + } + Err(e) => panic!("Validation should have succeeded: {e}"), + } + + // Pattern 4: Fallback chain + let config_value = rsproperties::get_or( + "config.primary", + rsproperties::get_or("config.secondary", 42), + ); + assert_eq!(config_value, 42); // Falls back to final default + + // Pattern 5: Type-safe configuration loading + struct AppConfig { + name: String, + version: u32, + debug: bool, + timeout: u64, + } + + // Set configuration properties + rsproperties::set("app.name", "TestApp")?; + rsproperties::set("app.version", &100u32)?; + rsproperties::set("app.debug", &false)?; + rsproperties::set("app.timeout", &30000u64)?; + + // Load configuration safely + let config = AppConfig { + name: rsproperties::get_or("app.name", "DefaultApp".to_string()), + version: rsproperties::get_or("app.version", 1), + debug: rsproperties::get_or("app.debug", false), + timeout: rsproperties::get_or("app.timeout", 5000), + }; + + assert_eq!(config.name, "TestApp"); + assert_eq!(config.version, 100); + assert!(!config.debug); + assert_eq!(config.timeout, 30000); + + println!("✅ Safe programming patterns demonstrated successfully!"); + + Ok(()) +} + +/// Performance and concurrent usage demonstration +#[tokio::test] +async fn test_performance_and_concurrency() -> anyhow::Result<()> { + setup_test_env().await; + + // Concurrent property operations + let handles: Vec<_> = (0..10) + .map(|i| { + tokio::spawn(async move { + let prop_name = format!("perf.test.prop_{i}"); + + // Set different types concurrently + match i % 3 { + 0 => { + rsproperties::set(&prop_name, &(i * 100))?; + let value: i32 = rsproperties::get(&prop_name)?; + assert_eq!(value, i * 100); + } + 1 => { + rsproperties::set(&prop_name, &(i as f64 * 1.5))?; + let value: f64 = rsproperties::get(&prop_name)?; + assert!((value - (i as f64 * 1.5)).abs() < f64::EPSILON); + } + 2 => { + rsproperties::set(&prop_name, &format!("string_{i}"))?; + let value: String = rsproperties::get(&prop_name)?; + assert_eq!(value, format!("string_{i}")); + } + _ => unreachable!(), + } + + anyhow::Ok(i) + }) + }) + .collect(); + + // Wait for all concurrent operations to complete + let mut results = Vec::new(); + for handle in handles { + results.push(handle.await??); + } + + // Verify all tasks completed + assert_eq!(results.len(), 10); + for (i, result) in results.iter().enumerate() { + assert_eq!(*result, i as i32); + } + + println!("✅ Concurrent operations completed successfully!"); + + // Performance measurement + let start_time = std::time::Instant::now(); + let num_ops = 50; // Reduced for reliability + + for i in 0..num_ops { + let prop_name = format!("perf.measurement.{i}"); + + // Combined set/get/parse operation + rsproperties::set(&prop_name, &i)?; + let _retrieved: String = rsproperties::get(&prop_name)?; + let _parsed: i32 = rsproperties::get(&prop_name)?; + } + + let elapsed = start_time.elapsed(); + let ops_per_second = (num_ops as f64 * 3.0) / elapsed.as_secs_f64(); // 3 operations per iteration + + println!("✅ Performance: {ops_per_second:.0} operations/second"); + + // Should be reasonably fast + assert!( + elapsed.as_millis() < 1000, + "Operations took too long: {elapsed:?}" + ); + + Ok(()) +} + +/// Summary test showcasing all key features +#[tokio::test] +async fn test_complete_feature_summary() -> anyhow::Result<()> { + setup_test_env().await; + + println!("🚀 Testing rsproperties new APIs - Complete Feature Summary"); + + // Feature 1: Enhanced Property Setting + println!("📝 Feature 1: Enhanced Property Setting"); + rsproperties::set("demo.string_prop", "Hello World")?; + rsproperties::set("demo.int_prop", &42i32)?; + rsproperties::set("demo.float_prop", &2.5f64)?; + rsproperties::set("demo.bool_prop", &true)?; + println!(" ✅ Properties set using set() and set() functions"); + + // Feature 2: Type-Safe Property Parsing + println!("📊 Feature 2: Type-Safe Property Parsing"); + let string_val: String = rsproperties::get("demo.string_prop")?; + let int_val: i32 = rsproperties::get("demo.int_prop")?; + let float_val: f64 = rsproperties::get("demo.float_prop")?; + let bool_val: bool = rsproperties::get("demo.bool_prop")?; + + assert_eq!(string_val, "Hello World"); + assert_eq!(int_val, 42); + assert!((float_val - 2.5).abs() < f64::EPSILON); + assert!(bool_val); + println!(" ✅ Type-safe parsing with get_parsed() function"); + + // Feature 3: Safe Defaults + println!("🛡️ Feature 3: Safe Defaults"); + let safe_int: i32 = rsproperties::get_or("demo.missing_prop", 999); + let safe_str: String = rsproperties::get_or("demo.missing_string", "default".to_string()); + + assert_eq!(safe_int, 999); + assert_eq!(safe_str, "default"); + println!(" ✅ Safe defaults with get_parsed_with_default() function"); + + // Feature 4: Error Handling + println!("⚠️ Feature 4: Error Handling"); + let error_result: Result = rsproperties::get("demo.nonexistent"); + assert!(error_result.is_err()); + + rsproperties::set("demo.invalid_int", "not_a_number")?; + let parse_error: Result = rsproperties::get("demo.invalid_int"); + assert!(parse_error.is_err()); + println!(" ✅ Proper error handling for missing and invalid properties"); + + // Feature 5: Backward Compatibility + println!("🔄 Feature 5: Backward Compatibility"); + let traditional_get: String = rsproperties::get("demo.string_prop")?; + let traditional_with_default = rsproperties::get_or("demo.int_prop", "0".to_string()); + + assert_eq!(traditional_get, "Hello World"); + assert_eq!(traditional_with_default, "42"); + println!(" ✅ Full backward compatibility with existing APIs"); + + println!("🎉 All new rsproperties API features working correctly!"); + println!(" - Enhanced property setting with type safety"); + println!(" - Type-safe property parsing"); + println!(" - Safe defaults and error handling"); + println!(" - Full backward compatibility"); + println!(" - Concurrent operation support"); + + Ok(()) +} diff --git a/libs/rust/rsproperties-service/tests/parsed_api_tests.rs b/libs/rust/rsproperties-service/tests/parsed_api_tests.rs new file mode 100644 index 0000000..f951a27 --- /dev/null +++ b/libs/rust/rsproperties-service/tests/parsed_api_tests.rs @@ -0,0 +1,319 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Tests for parsed property API functions +//! +//! These tests verify the functionality of the newly added parsed property APIs: +//! - get_parsed_with_default() - Parse property values with default fallback +//! - get_parsed() - Parse property values with error handling +//! - Type safety and parsing validation + +#[path = "common.rs"] +mod common; +use common::init_test; + +async fn setup_test_env() { + let _ = env_logger::builder().is_test(true).try_init(); + init_test().await; +} + +#[tokio::test] +async fn test_get_parsed_with_default_integers() -> anyhow::Result<()> { + setup_test_env().await; + + // Test with existing integer property + let prop_name = "test.parsed.int"; + let test_value = 42; + rsproperties::set(prop_name, &test_value)?; + + let parsed_value: i32 = rsproperties::get_or(prop_name, 0); + assert_eq!(parsed_value, test_value); + + // Test with different integer types + let parsed_i64: i64 = rsproperties::get_or(prop_name, 0i64); + assert_eq!(parsed_i64, test_value as i64); + + let parsed_u32: u32 = rsproperties::get_or(prop_name, 0u32); + assert_eq!(parsed_u32, test_value as u32); + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_with_default_nonexistent_property() { + setup_test_env().await; + + let prop_name = "nonexistent.parsed.property"; + let default_value = 123; + + let result: i32 = rsproperties::get_or(prop_name, default_value); + assert_eq!(result, default_value); +} + +#[tokio::test] +async fn test_get_parsed_with_default_invalid_parsing() -> anyhow::Result<()> { + setup_test_env().await; + + // Set a property with non-numeric value + let prop_name = "test.parsed.invalid"; + rsproperties::set(prop_name, "not_a_number")?; + + // Should return default value when parsing fails + let default_value = 999; + let result: i32 = rsproperties::get_or(prop_name, default_value); + assert_eq!(result, default_value); + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_with_default_booleans() -> anyhow::Result<()> { + setup_test_env().await; + + // Test boolean parsing - Rust's bool::from_str() is case-sensitive + let valid_cases = [ + ("test.bool.true", "true", true), + ("test.bool.false", "false", false), + ]; + + let invalid_cases = [ + ("test.bool.true_upper", "True"), + ("test.bool.true_all_upper", "TRUE"), + ("test.bool.false_upper", "False"), + ("test.bool.false_all_upper", "FALSE"), + ("test.bool.one", "1"), + ("test.bool.zero", "0"), + ("test.bool.yes", "yes"), + ("test.bool.no", "no"), + ]; + + // Test valid boolean strings + for (prop_name, prop_value, expected) in valid_cases.iter() { + rsproperties::set(prop_name, prop_value)?; + let result: bool = rsproperties::get_or(prop_name, false); + assert_eq!( + result, *expected, + "Failed for valid boolean property {prop_name} with value {prop_value}" + ); + } + + // Test invalid boolean strings (should return default) + for (prop_name, prop_value) in invalid_cases.iter() { + rsproperties::set(prop_name, prop_value)?; + let result: bool = rsproperties::get_or(prop_name, true); // Use true as default to verify fallback + assert!( + result, + "Should return default for invalid boolean value: {prop_value}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_with_default_floats() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.parsed.float"; + let test_value = std::f64::consts::PI; + rsproperties::set(prop_name, &test_value)?; + + let parsed_f32: f32 = rsproperties::get_or(prop_name, 0.0f32); + assert!((parsed_f32 - test_value as f32).abs() < f32::EPSILON); + + let parsed_f64: f64 = rsproperties::get_or(prop_name, 0.0f64); + assert!((parsed_f64 - test_value).abs() < f64::EPSILON); + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_success() -> anyhow::Result<()> { + setup_test_env().await; + + // Test successful parsing of various types + let prop_name_int = "test.get_parsed.int"; + let test_int = 12345; + rsproperties::set(prop_name_int, &test_int)?; + + let result: i32 = rsproperties::get(prop_name_int)?; + assert_eq!(result, test_int); + + // Test with string parsing + let prop_name_str = "test.get_parsed.string"; + let test_str = "hello_world"; + rsproperties::set(prop_name_str, test_str)?; + + let result: String = rsproperties::get(prop_name_str)?; + assert_eq!(result, test_str); + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_nonexistent_property() { + setup_test_env().await; + + let prop_name = "completely.nonexistent.property"; + let result: Result = rsproperties::get(prop_name); + + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("NotFound") || error_msg.contains("not found")); +} + +#[tokio::test] +async fn test_get_parsed_parsing_error() -> anyhow::Result<()> { + setup_test_env().await; + + // Set a property with invalid value for parsing + let prop_name = "test.get_parsed.invalid"; + rsproperties::set(prop_name, "definitely_not_a_number")?; + + let result: Result = rsproperties::get(prop_name); + + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("Parse") || error_msg.contains("parse")); + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_empty_property() -> anyhow::Result<()> { + setup_test_env().await; + + // Set a property with empty value + let prop_name = "test.get_parsed.empty"; + rsproperties::set(prop_name, "")?; + + let result: Result = rsproperties::get(prop_name); + + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("cannot parse integer from empty string")); + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_complex_types() -> anyhow::Result<()> { + setup_test_env().await; + + // Test parsing of complex numeric types + let prop_name = "test.parsed.complex"; + + // Test u64 + let large_value = u64::MAX / 2; + rsproperties::set(prop_name, &large_value)?; + let parsed_u64: u64 = rsproperties::get(prop_name)?; + assert_eq!(parsed_u64, large_value); + + // Test i64 negative + let negative_value = -12345678901234i64; + rsproperties::set(prop_name, &negative_value)?; + let parsed_i64: i64 = rsproperties::get(prop_name)?; + assert_eq!(parsed_i64, negative_value); + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_edge_cases() -> anyhow::Result<()> { + setup_test_env().await; + + // Test edge cases for parsing + let test_cases = [ + ("test.parsed.zero", "0", 0i32), + ("test.parsed.negative", "-1", -1i32), + ("test.parsed.max_i32", &i32::MAX.to_string(), i32::MAX), + ("test.parsed.min_i32", &i32::MIN.to_string(), i32::MIN), + ]; + + for (prop_name, prop_value, expected) in test_cases.iter() { + rsproperties::set(prop_name, prop_value)?; + let result: i32 = rsproperties::get(prop_name)?; + assert_eq!( + result, *expected, + "Failed for property {prop_name} with value {prop_value}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_get_parsed_with_whitespace() -> anyhow::Result<()> { + setup_test_env().await; + + // Test parsing values with whitespace (should be trimmed by the property system) + let prop_name = "test.parsed.whitespace"; + let test_value = " 123 "; + rsproperties::set(prop_name, test_value)?; + + // The property system should handle whitespace appropriately + let result: Result = rsproperties::get(prop_name); + + // Note: The behavior here depends on how the property system handles whitespace + // We'll just verify that the function behaves consistently + match result { + Ok(value) => assert_eq!(value, 123), + Err(_) => { + // If whitespace causes parsing to fail, that's also acceptable behavior + // as it depends on the underlying property system implementation + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_parsed_api_type_safety() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.type_safety"; + let test_value = "42"; + rsproperties::set(prop_name, test_value)?; + + // Verify that different numeric types parse correctly from the same string + let as_i32: i32 = rsproperties::get(prop_name)?; + let as_u32: u32 = rsproperties::get(prop_name)?; + let as_i64: i64 = rsproperties::get(prop_name)?; + let as_f64: f64 = rsproperties::get(prop_name)?; + + assert_eq!(as_i32, 42); + assert_eq!(as_u32, 42); + assert_eq!(as_i64, 42); + assert_eq!(as_f64, 42.0); + + Ok(()) +} + +#[tokio::test] +async fn test_parsed_api_with_real_world_properties() -> anyhow::Result<()> { + setup_test_env().await; + + // Test with property names that simulate real Android system properties + let test_cases = [ + ("ro.build.version.sdk", "34", 34i32), + ("ro.debuggable", "0", 0i32), + ("dalvik.vm.heapsize", "512", 512i32), + ("debug.my_app.timeout", "5000", 5000i32), + ("persist.vendor.radio.enable", "1", 1i32), + ]; + + for (prop_name, prop_value, expected) in test_cases.iter() { + rsproperties::set(prop_name, prop_value)?; + + let parsed_result: i32 = rsproperties::get(prop_name)?; + assert_eq!(parsed_result, *expected, "Failed for property {prop_name}"); + + let with_default: i32 = rsproperties::get_or(prop_name, 999); + assert_eq!( + with_default, *expected, + "get_parsed_with_default failed for property {prop_name}" + ); + } + + Ok(()) +} diff --git a/libs/rust/rsproperties-service/tests/performance_tests.rs b/libs/rust/rsproperties-service/tests/performance_tests.rs new file mode 100644 index 0000000..7edb717 --- /dev/null +++ b/libs/rust/rsproperties-service/tests/performance_tests.rs @@ -0,0 +1,381 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Performance and stress tests for rsproperties public API +//! +//! These tests verify performance characteristics and robustness +//! under various stress conditions. + +use rsproperties::{self, Result}; +use std::sync::{Arc, Barrier}; +use std::thread; +use std::time::{Duration, Instant}; + +#[path = "common.rs"] +mod common; +use common::init_test; + +async fn setup_perf_test_env() { + let _ = env_logger::builder().is_test(true).try_init(); + init_test().await; +} + +#[tokio::test] +async fn test_property_get_performance() -> Result<()> { + setup_perf_test_env().await; + + // Set up some test properties + let test_props = vec![ + ("perf.test.prop1", "value1"), + ("perf.test.prop2", "value2"), + ("perf.test.prop3", "value3"), + ("perf.test.prop4", "value4"), + ("perf.test.prop5", "value5"), + ]; + + for (name, value) in &test_props { + rsproperties::set(name, value)?; + } + + // Measure get performance + let iterations = 10000; + let start = Instant::now(); + + for i in 0..iterations { + let prop_name = &test_props[i % test_props.len()].0; + let _value: String = rsproperties::get(prop_name).unwrap_or_default(); + } + + let elapsed = start.elapsed(); + let avg_time = elapsed / iterations as u32; + + println!("Get performance: {iterations} iterations in {elapsed:?}"); + println!("Average time per get: {avg_time:?}"); + println!( + "Gets per second: {:.0}", + iterations as f64 / elapsed.as_secs_f64() + ); + + // Verify performance is reasonable (less than 100μs per get on average) + assert!( + avg_time < Duration::from_micros(100), + "Get operation too slow: {avg_time:?} per operation" + ); + + Ok(()) +} + +#[tokio::test] +async fn test_property_get_or_performance() -> Result<()> { + setup_perf_test_env().await; + + // Test both existing and non-existing properties + rsproperties::set("existing.perf.prop", "existing_value")?; + + let iterations = 5000; + + // Test existing property performance + let start = Instant::now(); + for _ in 0..iterations { + let _value = rsproperties::get_or("existing.perf.prop", "default".to_string()); + } + let existing_elapsed = start.elapsed(); + + // Test non-existing property performance + let start = Instant::now(); + for _ in 0..iterations { + let _value = rsproperties::get_or("nonexistent.perf.prop", "default".to_string()); + } + let nonexistent_elapsed = start.elapsed(); + + println!("get_or performance:"); + println!( + " Existing property: {} ops in {:?} ({:?} avg)", + iterations, + existing_elapsed, + existing_elapsed / iterations + ); + println!( + " Non-existing property: {} ops in {:?} ({:?} avg)", + iterations, + nonexistent_elapsed, + nonexistent_elapsed / iterations + ); + + // Both should be reasonably fast + assert!(existing_elapsed / iterations < Duration::from_micros(60)); + assert!(nonexistent_elapsed / iterations < Duration::from_micros(50)); + + Ok(()) +} + +#[tokio::test] +async fn test_property_set_performance() -> Result<()> { + setup_perf_test_env().await; + + let iterations = 100; + let start = Instant::now(); + + for i in 0..iterations { + let prop_name = format!("perf.set.prop.{i}"); + let prop_value = format!("value_{i}"); + rsproperties::set(&prop_name, &prop_value)?; + } + + let elapsed = start.elapsed(); + let avg_time = elapsed / iterations; + + println!("Set performance: {iterations} iterations in {elapsed:?}"); + println!("Average time per set: {avg_time:?}"); + println!( + "Sets per second: {:.0}", + iterations as f64 / elapsed.as_secs_f64() + ); + + // Verify set performance (should be under 1ms per operation) + assert!( + avg_time < Duration::from_millis(1), + "Set operation too slow: {avg_time:?} per operation" + ); + + Ok(()) +} + +#[tokio::test] +async fn test_large_property_values() -> Result<()> { + setup_perf_test_env().await; + + // Test with various sizes approaching PROP_VALUE_MAX + let sizes = vec![10, 50, 80, rsproperties::PROP_VALUE_MAX - 5]; + + for size in sizes { + let prop_name = format!("perf.large.prop.{size}"); + let large_value = "x".repeat(size); + + let start = Instant::now(); + rsproperties::set(&prop_name, &large_value)?; + let set_time = start.elapsed(); + + let start = Instant::now(); + let retrieved: String = rsproperties::get(&prop_name)?; + let get_time = start.elapsed(); + + assert_eq!(retrieved, large_value); + + println!("Size {size}: set={set_time:?}, get={get_time:?}"); + + // Performance should not degrade significantly with size + assert!(set_time < Duration::from_millis(10)); + assert!(get_time < Duration::from_millis(1)); + } + + Ok(()) +} + +#[tokio::test] +async fn test_concurrent_reads() -> Result<()> { + setup_perf_test_env().await; + + // Set up test properties + let num_props = 100; + for i in 0..num_props { + let prop_name = format!("concurrent.read.prop.{i}"); + let prop_value = format!("value_{i}"); + rsproperties::set(&prop_name, &prop_value)?; + } + + let num_threads = 4; + let reads_per_thread = 1000; + let barrier = Arc::new(Barrier::new(num_threads)); + + let handles: Vec<_> = (0..num_threads) + .map(|thread_id| { + let barrier = Arc::clone(&barrier); + + thread::spawn(move || { + barrier.wait(); // Synchronize start + + let start = Instant::now(); + for i in 0..reads_per_thread { + let prop_name = format!("concurrent.read.prop.{}", i % num_props); + let expected = format!("value_{}", i % num_props); + let value: String = rsproperties::get(&prop_name).unwrap_or_default(); + assert_eq!(value, expected, "Failed to get property {prop_name}"); + } + let elapsed = start.elapsed(); + + println!("Thread {thread_id} completed {reads_per_thread} reads in {elapsed:?}"); + elapsed + }) + }) + .collect(); + + let mut total_time = Duration::new(0, 0); + for handle in handles { + let thread_time = handle.join().unwrap(); + total_time += thread_time; + } + + let total_reads = num_threads * reads_per_thread; + println!( + "Concurrent reads: {} total reads, avg thread time: {:?}", + total_reads, + total_time / num_threads as u32 + ); + + Ok(()) +} + +#[tokio::test] +async fn test_concurrent_writes() -> Result<()> { + setup_perf_test_env().await; + + let num_threads = 3; + let writes_per_thread = 20; + let barrier = Arc::new(Barrier::new(num_threads)); + + let handles: Vec<_> = (0..num_threads) + .map(|thread_id| { + let barrier = Arc::clone(&barrier); + + thread::spawn(move || -> Result { + barrier.wait(); // Synchronize start + + let start = Instant::now(); + for i in 0..writes_per_thread { + let prop_name = format!("concurrent.write.{thread_id}.prop.{i}"); + let prop_value = format!("thread_{thread_id}_value_{i}"); + rsproperties::set(&prop_name, &prop_value)?; + } + let elapsed = start.elapsed(); + + println!("Thread {thread_id} completed {writes_per_thread} writes in {elapsed:?}"); + Ok(elapsed) + }) + }) + .collect(); + + let mut total_time = Duration::new(0, 0); + for handle in handles { + let thread_time = handle.join().unwrap()?; + total_time += thread_time; + } + + // Verify writes completed + for thread_id in 0..num_threads { + for i in 0..writes_per_thread { + let prop_name = format!("concurrent.write.{thread_id}.prop.{i}"); + let expected = format!("thread_{thread_id}_value_{i}"); + let value: String = rsproperties::get(&prop_name).unwrap_or_default(); + assert_eq!(value, expected); + } + } + + let total_writes = num_threads * writes_per_thread; + println!( + "Concurrent writes: {} total writes, avg thread time: {:?}", + total_writes, + total_time / num_threads as u32 + ); + + Ok(()) +} + +#[tokio::test] +async fn test_mixed_read_write_workload() -> Result<()> { + setup_perf_test_env().await; + + // Set up initial properties + for i in 0..50 { + let prop_name = format!("mixed.initial.prop.{i}"); + let prop_value = format!("initial_value_{i}"); + rsproperties::set(&prop_name, &prop_value)?; + } + + let num_threads = 4; + let operations_per_thread = 50; + let barrier = Arc::new(Barrier::new(num_threads)); + + let handles: Vec<_> = (0..num_threads) + .map(|thread_id| { + let barrier = Arc::clone(&barrier); + + thread::spawn(move || -> Result<()> { + barrier.wait(); + + let start = Instant::now(); + for i in 0..operations_per_thread { + if i % 3 == 0 { + // Write operation + let prop_name = format!("mixed.thread.{thread_id}.prop.{i}"); + let prop_value = format!("value_{thread_id}_{i}"); + rsproperties::set(&prop_name, &prop_value)?; + } else { + // Read operation + let prop_name = format!("mixed.initial.prop.{}", i % 50); + let _value: String = rsproperties::get(&prop_name).unwrap_or_default(); + } + } + let elapsed = start.elapsed(); + + println!("Mixed workload thread {thread_id} completed in {elapsed:?}"); + Ok(()) + }) + }) + .collect(); + + for handle in handles { + handle.join().unwrap()?; + } + + println!("Mixed read/write workload completed successfully"); + + Ok(()) +} + +#[tokio::test] +async fn test_property_name_patterns() -> Result<()> { + setup_perf_test_env().await; + + // Test various property name patterns + let patterns = vec![ + ("short", "value"), + ("medium.length.property", "value"), + ( + "very.long.property.name.with.many.segments.and.dots", + "value", + ), + ("property_with_underscores", "value"), + ("property.with.123.numbers", "value"), + ("ro.build.version.sdk", "33"), // Android-style + ("persist.sys.locale", "en-US"), // Android-style + ]; + + let iterations = 1000; + + for (prop_name, prop_value) in &patterns { + // Set the property + rsproperties::set(prop_name, prop_value)?; + + // Measure get performance for this pattern + let start = Instant::now(); + for _ in 0..iterations { + let _value: String = rsproperties::get(prop_name).unwrap_or_default(); + } + let elapsed = start.elapsed(); + + println!( + "Pattern '{}': {} gets in {:?} ({:?} avg)", + prop_name, + iterations, + elapsed, + elapsed / iterations + ); + + // Verify correctness + let retrieved: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved, *prop_value); + } + + Ok(()) +} diff --git a/libs/rust/rsproperties-service/tests/set_api_tests.rs b/libs/rust/rsproperties-service/tests/set_api_tests.rs new file mode 100644 index 0000000..3ecf1ec --- /dev/null +++ b/libs/rust/rsproperties-service/tests/set_api_tests.rs @@ -0,0 +1,536 @@ +// Copyright 2024 Jeff Kim +// SPDX-License-Identifier: Apache-2.0 + +//! Tests for property setting API functions +//! +//! These tests verify the functionality of the property setting APIs: +//! - set() - Set property with any Display type (includes strings) +//! - Type conversion and validation +//! - Error handling and edge cases +#![allow(clippy::approx_constant)] + +#[path = "common.rs"] +mod common; +use common::init_test; + +async fn setup_test_env() { + let _ = env_logger::builder().is_test(true).try_init(); + init_test().await; +} + +#[tokio::test] +async fn test_set_basic() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.set.basic"; + let prop_value = "test_string_value"; + + // Set property using set + rsproperties::set(prop_name, prop_value)?; + + // Verify the property was set correctly + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, prop_value); + + Ok(()) +} + +#[tokio::test] +async fn test_set_special_characters() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.set.special"; + let prop_value = "special!@#$%^&*()_+-={}[]|\\:;\"'<>?,./"; + + rsproperties::set(prop_name, prop_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, prop_value); + + Ok(()) +} + +#[tokio::test] +async fn test_set_unicode() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.set.unicode"; + let prop_value = "안녕하세요 🌍 こんにちは Здравствуй"; + + rsproperties::set(prop_name, prop_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, prop_value); + + Ok(()) +} + +#[tokio::test] +async fn test_set_whitespace() -> anyhow::Result<()> { + setup_test_env().await; + + let test_cases = [ + ("test.set.spaces", "value with spaces"), + ("test.set.tabs", "value\twith\ttabs"), + ("test.set.newlines", "value\nwith\nnewlines"), + ("test.set.leading_space", " leading_space"), + ("test.set.trailing_space", "trailing_space "), + ("test.set.multiple_spaces", "multiple spaces here"), + ]; + + for (prop_name, prop_value) in test_cases.iter() { + rsproperties::set(prop_name, prop_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!( + retrieved_value, *prop_value, + "Failed for property {prop_name}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_set_display_integers() -> anyhow::Result<()> { + setup_test_env().await; + + // Test i8 + let prop_name = "test.set.i8"; + let test_value = i8::MAX; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test i16 + let prop_name = "test.set.i16"; + let test_value = i16::MAX; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test i32 + let prop_name = "test.set.i32"; + let test_value = i32::MAX; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test i64 + let prop_name = "test.set.i64"; + let test_value = i64::MAX; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test u8 + let prop_name = "test.set.u8"; + let test_value = u8::MAX; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test u16 + let prop_name = "test.set.u16"; + let test_value = u16::MAX; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test u32 + let prop_name = "test.set.u32"; + let test_value = u32::MAX; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test u64 (using a smaller value to avoid potential issues) + let prop_name = "test.set.u64"; + let test_value = u64::MAX / 2; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_set_display_negative_integers() -> anyhow::Result<()> { + setup_test_env().await; + + // Test i8 min + let prop_name = "test.set.neg_i8"; + let test_value = i8::MIN; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test i16 min + let prop_name = "test.set.neg_i16"; + let test_value = i16::MIN; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test i32 min + let prop_name = "test.set.neg_i32"; + let test_value = i32::MIN; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test i64 min + let prop_name = "test.set.neg_i64"; + let test_value = i64::MIN; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test zero + let prop_name = "test.set.neg_zero"; + let test_value = 0i32; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test small negative + let prop_name = "test.set.neg_small"; + let test_value = -42i32; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_set_display_floats() -> anyhow::Result<()> { + setup_test_env().await; + + // Test f32 + let prop_name = "test.set.f32"; + let test_value = 3.14159f32; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test f64 + let prop_name = "test.set.f64"; + let test_value = 2.718281828459045f64; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test f32 zero + let prop_name = "test.set.f32_zero"; + let test_value = 0.0f32; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test f64 negative + let prop_name = "test.set.f64_negative"; + let test_value = -123.456f64; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test f32 small + let prop_name = "test.set.f32_small"; + let test_value = f32::EPSILON; + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + // Test f64 large (use smaller value to avoid length limits) + let prop_name = "test.set.f64_large"; + let test_value = 1e10f64; // Smaller than 1e100 to fit in 92 chars + rsproperties::set(prop_name, &test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, test_value.to_string()); + + Ok(()) +} + +#[tokio::test] +async fn test_set_display_booleans() -> anyhow::Result<()> { + setup_test_env().await; + + let test_cases = [ + ("test.set.bool_true", true, "true"), + ("test.set.bool_false", false, "false"), + ]; + + for (prop_name, test_value, expected_str) in test_cases.iter() { + rsproperties::set(prop_name, test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!( + retrieved_value, *expected_str, + "Failed for property {prop_name} with value {test_value}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_set_display_strings() -> anyhow::Result<()> { + setup_test_env().await; + + let test_cases = [ + ("test.set.string", "plain_string".to_string()), + ("test.set.string_empty", String::new()), + ("test.set.string_medium", "a".repeat(50)), // Shorter than 92 chars + ("test.set.string_unicode", "测试🚀".to_string()), + ]; + + for (prop_name, test_value) in test_cases.iter() { + rsproperties::set(prop_name, test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!( + retrieved_value, *test_value, + "Failed for property {prop_name} with value '{test_value}'" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_set_display_string_slices() -> anyhow::Result<()> { + setup_test_env().await; + + let test_cases = [ + ("test.set.str_slice", "string_slice_value"), + ("test.set.str_ref", "string_reference"), + ("test.set.str_borrowed", "borrowed_string"), + ]; + + for (prop_name, test_value) in test_cases.iter() { + rsproperties::set(prop_name, test_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!( + retrieved_value, + test_value.to_string(), + "Failed for property {prop_name}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_set_overwrite_existing() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.set.overwrite"; + + // Set initial value + let initial_value = "initial_value"; + rsproperties::set(prop_name, initial_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, initial_value); + + // Overwrite with different type + let new_value = 12345i32; + rsproperties::set(prop_name, &new_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, new_value.to_string()); + + // Overwrite with another string + let final_value = "final_value"; + rsproperties::set(prop_name, final_value)?; + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!(retrieved_value, final_value); + + Ok(()) +} + +#[tokio::test] +async fn test_set_multiple_properties() -> anyhow::Result<()> { + setup_test_env().await; + + let properties = [ + ("test.multi.int", "42"), + ("test.multi.float", "3.14"), + ("test.multi.bool", "true"), + ("test.multi.string", "hello"), + ("test.multi.negative", "-999"), + ]; + + // Set all properties + for (prop_name, prop_value) in properties.iter() { + rsproperties::set(prop_name, prop_value)?; + } + + // Verify all properties were set correctly + for (prop_name, expected_value) in properties.iter() { + let retrieved_value: String = rsproperties::get(prop_name)?; + assert_eq!( + retrieved_value, *expected_value, + "Failed for property {prop_name}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_set_property_name_validation() { + setup_test_env().await; + + // Test various property name patterns + let valid_names = [ + "test.valid.name", + "ro.build.version.sdk", + "debug.my_app.enabled", + "persist.vendor.radio.config", + "sys.boot_completed", + "dalvik.vm.heapsize", + "test.123.numbered", + "test.with-dashes", + "test.with_underscores", + ]; + + for prop_name in valid_names.iter() { + let result = rsproperties::set(prop_name, "test_value"); + assert!( + result.is_ok(), + "Valid property name should succeed: {prop_name}" + ); + } +} + +#[tokio::test] +async fn test_set_and_get_integration() -> anyhow::Result<()> { + setup_test_env().await; + + let prop_name = "test.integration.complete"; + + // Test the complete cycle: set -> get -> parse + let original_value = 98765i32; + + // Set using Display trait + rsproperties::set(prop_name, &original_value)?; + + // Get as string + let string_value: String = rsproperties::get(prop_name)?; + assert_eq!(string_value, original_value.to_string()); + + // Parse back to integer + let parsed_value: i32 = rsproperties::get(prop_name)?; + assert_eq!(parsed_value, original_value); + + // Get with default (should return the actual value, not default) + let with_default: i32 = rsproperties::get_or(prop_name, 999); + assert_eq!(with_default, original_value); + + Ok(()) +} + +#[tokio::test] +async fn test_set_concurrent_properties() -> anyhow::Result<()> { + setup_test_env().await; + + // Test setting properties concurrently (basic test) + let handles: Vec<_> = (0..10) + .map(|i| { + tokio::spawn(async move { + let prop_name = format!("test.concurrent.prop_{i}"); + let prop_value = format!("value_{i}"); + + rsproperties::set(&prop_name, &prop_value).unwrap(); + + let retrieved: String = rsproperties::get(&prop_name).unwrap(); + assert_eq!(retrieved, prop_value); + + (prop_name, prop_value) + }) + }) + .collect(); + + // Wait for all tasks to complete + for handle in handles { + let (prop_name, expected_value) = handle.await?; + let final_value: String = rsproperties::get(&prop_name)?; + assert_eq!(final_value, expected_value); + } + + Ok(()) +} + +#[tokio::test] +async fn test_set_real_world_android_properties() -> anyhow::Result<()> { + setup_test_env().await; + + // Test setting properties that simulate real Android system properties + let properties = [ + ("debug.real_world_test.log_level", "verbose"), + ("debug.real_world_test.timeout_ms", "5000"), + ("debug.real_world_test.enabled", "true"), + ("persist.real_world_test.feature", "enabled"), + ("sys.real_world_test.ready", "1"), + ("real_world_test.app.version", "1.2.3"), + ("real_world_test.app.build_number", "456"), + ("test.real_world.iterations", "1000"), + ("test.real_world.batch_size", "128"), + ("vendor.real_world_test.calibration", "0.95"), + ]; + + for (prop_name, prop_value) in properties.iter() { + rsproperties::set(prop_name, prop_value)?; + let retrieved: String = rsproperties::get(prop_name)?; + assert_eq!( + retrieved, *prop_value, + "Failed for Android-style property {prop_name}" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_set_error_handling() { + setup_test_env().await; + + // Test edge cases for error handling + let very_long_name = "a".repeat(1000); + let very_long_value = "b".repeat(10000); + + // Very long property name - should either succeed or fail gracefully + let result = rsproperties::set(&very_long_name, "test"); + // We don't assert success/failure as it depends on system limits + // But we ensure it doesn't panic + match result { + Ok(_) => println!("Long property name was accepted"), + Err(e) => println!("Long property name was rejected: {e}"), + } + + // Very long property value - should either succeed or fail gracefully + let result = rsproperties::set("test.long.value", &very_long_value); + match result { + Ok(_) => println!("Long property value was accepted"), + Err(e) => println!("Long property value was rejected: {e}"), + } +} + +#[tokio::test] +async fn test_set_consistency_between_set_and_set() -> anyhow::Result<()> { + setup_test_env().await; + + let test_value = "consistent_test_value"; + + // Set using set with &str + let prop_name1 = "test.consistency.set_str"; + rsproperties::set(prop_name1, test_value)?; + + // Set using set with String + let prop_name2 = "test.consistency.set"; + rsproperties::set(prop_name2, &test_value.to_string())?; + + // Both should produce the same result + let value1: String = rsproperties::get(prop_name1)?; + let value2: String = rsproperties::get(prop_name2)?; + + assert_eq!(value1, value2); + assert_eq!(value1, test_value); + + Ok(()) +} diff --git a/libs/rust/src/consts.rs b/libs/rust/src/consts.rs new file mode 100644 index 0000000..80f78b1 --- /dev/null +++ b/libs/rust/src/consts.rs @@ -0,0 +1,9 @@ +/// Magic prefix used by the km_compat C++ code to mark a key that is owned by an +/// underlying Keymaster hardware device that has been wrapped by km_compat. (The +/// final zero byte indicates that the blob is not software emulated.) +pub const KEYMASTER_BLOB_HW_PREFIX: &[u8] = b"pKMblob\x00"; + +/// Magic prefix used by the km_compat C++ code to mark a key that is owned by an +/// software emulation device that has been wrapped by km_compat. (The final one +/// byte indicates that the blob is software emulated.) +pub const KEYMASTER_BLOB_SW_PREFIX: &[u8] = b"pKMblob\x01"; diff --git a/libs/rust/src/global.rs b/libs/rust/src/global.rs new file mode 100644 index 0000000..b825b02 --- /dev/null +++ b/libs/rust/src/global.rs @@ -0,0 +1,92 @@ +use std::{ + cell::RefCell, + sync::{Arc, LazyLock, Mutex, Once, RwLock}, +}; + +use rsbinder::Strong; + +use anyhow::{Context, Result}; + +use crate::{ + android::hardware::security::secureclock::ISecureClock::ISecureClock, + err, + keymaster::{db::KeymasterDb, enforcements::Enforcements, super_key::SuperKeyManager}, +}; + +static DB_INIT: Once = Once::new(); + +/// Open a connection to the Keystore 2.0 database. This is called during the initialization of +/// the thread local DB field. It should never be called directly. The first time this is called +/// we also call KeystoreDB::cleanup_leftovers to restore the key lifecycle invariant. See the +/// documentation of cleanup_leftovers for more details. The function also constructs a blob +/// garbage collector. The initializing closure constructs another database connection without +/// a gc. Although one GC is created for each thread local database connection, this closure +/// is run only once, as long as the ASYNC_TASK instance is the same. So only one additional +/// database connection is created for the garbage collector worker. +pub fn create_thread_local_db() -> KeymasterDb { + let result = KeymasterDb::new(); + let mut db = match result { + Ok(db) => db, + Err(e) => { + log::error!("Failed to open Keystore database: {e:?}"); + log::error!("Has /data been mounted correctly?"); + panic!("Failed to open database for Keystore, cannot continue: {e:?}") + } + }; + + DB_INIT.call_once(|| { + log::info!("Touching Keystore 2.0 database for this first time since boot."); + log::info!("Calling cleanup leftovers."); + let n = db + .cleanup_leftovers() + .expect("Failed to cleanup database on startup"); + if n != 0 { + log::info!( + "Cleaned up {n} failed entries, indicating keystore crash on key generation" + ); + } + }); + db +} + +thread_local! { + /// Database connections are not thread safe, but connecting to the + /// same database multiple times is safe as long as each connection is + /// used by only one thread. So we store one database connection per + /// thread in this thread local key. + pub static DB: RefCell = RefCell::new(create_thread_local_db()); +} + +pub static ENFORCEMENTS: LazyLock = LazyLock::new(Default::default); + +pub static SUPER_KEY: LazyLock>> = LazyLock::new(Default::default); + +/// Timestamp service. +static TIME_STAMP_DEVICE: Mutex>> = Mutex::new(None); + +/// Get the timestamp service that verifies auth token timeliness towards security levels with +/// different clocks. +pub fn get_timestamp_service() -> Result> { + let mut ts_device = TIME_STAMP_DEVICE.lock().unwrap(); + if let Some(dev) = &*ts_device { + Ok(dev.clone()) + } else { + let dev = connect_secureclock().context(err!())?; + *ts_device = Some(dev.clone()); + Ok(dev) + } +} + +fn connect_secureclock() -> Result> { + let descriptors = ::descriptor(); + let dev: Strong = + rsbinder::hub::get_interface(descriptors).context(err!())?; + + Ok(dev) +} + +/// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime +/// 999912312359559, which is 253402300799000 ms from Jan 1, 1970. +pub const UNDEFINED_NOT_AFTER: i64 = 253402300799000i64; + +pub const AID_KEYSTORE: u32 = 1017; diff --git a/libs/rust/src/keymaster/attestation_key_utils.rs b/libs/rust/src/keymaster/attestation_key_utils.rs new file mode 100644 index 0000000..0200438 --- /dev/null +++ b/libs/rust/src/keymaster/attestation_key_utils.rs @@ -0,0 +1,117 @@ +// Copyright 2021, 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. + +//! Implements get_attestation_key_info which loads remote provisioned or user +//! generated attestation keys. + +use crate::android::hardware::security::keymint::ErrorCode::ErrorCode; +use crate::android::hardware::security::keymint::{ + AttestationKey::AttestationKey, KeyParameter::KeyParameter, +}; +use crate::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, +}; +use crate::err; +use crate::keymaster::db::{BlobMetaData, KeyEntryLoadBits, KeyIdGuard, KeyType, KeymasterDb}; +use crate::keymaster::error::KsError; +use anyhow::{Context, Result}; +use x509_cert::der::{Decode, Encode}; + +/// KeyMint takes two different kinds of attestation keys. Remote provisioned keys +/// and those that have been generated by the user. Unfortunately, they need to be +/// handled quite differently, thus the different representations. +pub enum AttestationKeyInfo { + RkpdProvisioned { + attestation_key: AttestationKey, + /// Concatenated chain of DER-encoded certificates (ending with the root). + attestation_certs: Vec, + }, + UserGenerated { + key_id_guard: KeyIdGuard, + blob: Vec, + blob_metadata: BlobMetaData, + issuer_subject: Vec, + }, +} + +/// This function loads and, optionally, assigns the caller's remote provisioned +/// attestation key if a challenge is present. Alternatively, if `attest_key_descriptor` is given, +/// it loads the user generated attestation key from the database. +pub fn get_attest_key_info( + _key: &KeyDescriptor, + caller_uid: u32, + attest_key_descriptor: Option<&KeyDescriptor>, + _params: &[KeyParameter], + db: &mut KeymasterDb, +) -> Result> { + match attest_key_descriptor { + None => Ok(None), + Some(attest_key) => get_user_generated_attestation_key(attest_key, caller_uid, db) + .context(err!("Trying to load attest key")) + .map(Some), + } +} + +fn get_user_generated_attestation_key( + key: &KeyDescriptor, + caller_uid: u32, + db: &mut KeymasterDb, +) -> Result { + let (key_id_guard, blob, cert, blob_metadata) = + load_attest_key_blob_and_cert(key, caller_uid, db) + .context(err!("Failed to load blob and cert"))?; + + let cert = x509_cert::Certificate::from_der(&cert) + .context(err!("Failed to parse subject from certificate"))?; + let issuer_subject = cert + .tbs_certificate + .subject + .to_der() + .context(err!("failed to DER-encode subject"))?; + + Ok(AttestationKeyInfo::UserGenerated { + key_id_guard, + blob, + issuer_subject, + blob_metadata, + }) +} + +fn load_attest_key_blob_and_cert( + key: &KeyDescriptor, + caller_uid: u32, + db: &mut KeymasterDb, +) -> Result<(KeyIdGuard, Vec, Vec, BlobMetaData)> { + match key.domain { + Domain::BLOB => Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("Domain::BLOB attestation keys not supported")), + _ => { + let (key_id_guard, mut key_entry) = db + .load_key_entry(key, KeyType::Client, KeyEntryLoadBits::BOTH, caller_uid) + .context(err!("Failed to load key."))?; + + let (blob, blob_metadata) = key_entry + .take_key_blob_info() + .ok_or(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(err!( + "Successfully loaded key entry, but KM blob was missing" + ))?; + let cert = key_entry + .take_cert() + .ok_or(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(err!("Successfully loaded key entry, but cert was missing"))?; + Ok((key_id_guard, blob, cert, blob_metadata)) + } + } +} diff --git a/libs/rust/src/keymaster/boot_key.rs b/libs/rust/src/keymaster/boot_key.rs new file mode 100644 index 0000000..e9e0352 --- /dev/null +++ b/libs/rust/src/keymaster/boot_key.rs @@ -0,0 +1,304 @@ +// Copyright 2021, 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. + +//! Offer keys based on the "boot level" for superencryption. + +use crate::android::hardware::security::keymint::{ + Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter as KmKeyParameter, + KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, +}; +use crate::err; +use crate::keymaster::db::{KeyType, KeymasterDb}; +use crate::keymaster::key_parameter::KeyParameterValue; +use crate::keymaster::keymint_device::KeyMintDevice; +use anyhow::{Context, Result}; +use kmr_common::crypto::AES_256_KEY_LENGTH; +use kmr_crypto_boring::km::hkdf_expand; +use kmr_crypto_boring::zvec::ZVec; +use std::collections::VecDeque; + +/// Strategies used to prevent later boot stages from using the KM key that protects the level 0 +/// key +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum DenyLaterStrategy { + /// set MaxUsesPerBoot to 1. This is much less secure, since the attacker can replace the key + /// itself, and therefore create artifacts which appear to come from early boot. + MaxUsesPerBoot, + /// set the EarlyBootOnly property. This property is only supported in KM from 4.1 on, but + /// it ensures that the level 0 key was genuinely created in early boot + EarlyBootOnly, +} + +/// Generally the L0 KM and strategy are chosen by probing KM versions in TEE and Strongbox. +/// However, once a device is launched the KM and strategy must never change, even if the +/// KM version in TEE or Strongbox is updated. Setting this property at build time using +/// `PRODUCT_VENDOR_PROPERTIES` means that the strategy can be fixed no matter what versions +/// of KM are present. +const PROPERTY_NAME: &str = "ro.keystore.boot_level_key.strategy"; + +fn lookup_level_zero_km_and_strategy() -> Result> { + let property_val: Result = rsproperties::get(PROPERTY_NAME); + + // TODO: use feature(let_else) when that's stabilized. + let property_val = if let Ok(p) = property_val { + p + } else { + log::info!( + "{} not set, inferring from installed KM instances", + PROPERTY_NAME + ); + return Ok(None); + }; + let (level, strategy) = if let Some(c) = property_val.split_once(':') { + c + } else { + log::error!("Missing colon in {}: {:?}", PROPERTY_NAME, property_val); + return Ok(None); + }; + let level = match level { + "TRUSTED_ENVIRONMENT" => SecurityLevel::TRUSTED_ENVIRONMENT, + "STRONGBOX" => SecurityLevel::STRONGBOX, + _ => { + log::error!("Unknown security level in {}: {:?}", PROPERTY_NAME, level); + return Ok(None); + } + }; + let strategy = match strategy { + "EARLY_BOOT_ONLY" => DenyLaterStrategy::EarlyBootOnly, + "MAX_USES_PER_BOOT" => DenyLaterStrategy::MaxUsesPerBoot, + _ => { + log::error!( + "Unknown DenyLaterStrategy in {}: {:?}", + PROPERTY_NAME, + strategy + ); + return Ok(None); + } + }; + log::info!("Set from {}: {}", PROPERTY_NAME, property_val); + Ok(Some((level, strategy))) +} + +fn get_level_zero_key_km_and_strategy() -> Result<(KeyMintDevice, DenyLaterStrategy)> { + if let Some((level, strategy)) = lookup_level_zero_km_and_strategy()? { + return Ok(( + KeyMintDevice::get(level).context(err!("Get KM instance failed."))?, + strategy, + )); + } + let tee = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) + .context(err!("Get TEE instance failed."))?; + if tee.version() >= KeyMintDevice::KEY_MASTER_V4_1 { + Ok((tee, DenyLaterStrategy::EarlyBootOnly)) + } else { + match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX) + .context(err!("Get Strongbox instance failed."))? + { + Some(strongbox) if strongbox.version() >= KeyMintDevice::KEY_MASTER_V4_1 => { + Ok((strongbox, DenyLaterStrategy::EarlyBootOnly)) + } + _ => Ok((tee, DenyLaterStrategy::MaxUsesPerBoot)), + } + } +} + +/// This is not thread safe; caller must hold a lock before calling. +/// In practice the caller is SuperKeyManager and the lock is the +/// Mutex on its internal state. +pub fn get_level_zero_key(db: &mut KeymasterDb) -> Result { + let (km_dev, deny_later_strategy) = + get_level_zero_key_km_and_strategy().context(err!("get preferred KM instance failed"))?; + log::info!( + "In get_level_zero_key: security_level={:?}, deny_later_strategy={:?}", + km_dev.security_level(), + deny_later_strategy + ); + let required_security_level = km_dev.security_level(); + let required_param: KmKeyParameter = match deny_later_strategy { + DenyLaterStrategy::EarlyBootOnly => KeyParameterValue::EarlyBootOnly, + DenyLaterStrategy::MaxUsesPerBoot => KeyParameterValue::MaxUsesPerBoot(1), + } + .into(); + let params = vec![ + KeyParameterValue::Algorithm(Algorithm::HMAC).into(), + KeyParameterValue::Digest(Digest::SHA_2_256).into(), + KeyParameterValue::KeySize(256).into(), + KeyParameterValue::MinMacLength(256).into(), + KeyParameterValue::KeyPurpose(KeyPurpose::SIGN).into(), + KeyParameterValue::NoAuthRequired.into(), + required_param.clone(), + ]; + + let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string()); + let (key_id_guard, key_entry) = km_dev + .lookup_or_generate_key( + db, + &key_desc, + KeyType::Client, + ¶ms, + |key_characteristics| { + key_characteristics.iter().any(|kc| { + if kc.securityLevel != required_security_level { + log::error!( + "In get_level_zero_key: security level expected={:?} got={:?}", + required_security_level, + kc.securityLevel + ); + return false; + } + if !kc.authorizations.iter().any(|a| a == &required_param) { + log::error!( + "In get_level_zero_key: required param absent {:?}", + required_param + ); + return false; + } + true + }) + }, + ) + .context(err!("lookup_or_generate_key failed"))?; + + let params = [ + KeyParameterValue::MacLength(256).into(), + KeyParameterValue::Digest(Digest::SHA_2_256).into(), + ]; + let level_zero_key = km_dev + .use_key_in_one_step( + db, + &key_id_guard, + &key_entry, + KeyPurpose::SIGN, + ¶ms, + None, + b"Create boot level key", + ) + .context(err!("use_key_in_one_step failed"))?; + // TODO: this is rather unsatisfactory, we need a better way to handle + // sensitive binder returns. + let level_zero_key = + ZVec::try_from(level_zero_key).context(err!("conversion to ZVec failed"))?; + Ok(level_zero_key) +} + +/// Holds the key for the current boot level, and a cache of future keys generated as required. +/// When the boot level advances, keys prior to the current boot level are securely dropped. +pub struct BootLevelKeyCache { + /// Least boot level currently accessible, if any is. + current: usize, + /// Invariant: cache entry *i*, if it exists, holds the HKDF key for boot level + /// *i* + `current`. If the cache is non-empty it can be grown forwards, but it cannot be + /// grown backwards, so keys below `current` are inaccessible. + /// `cache.clear()` makes all keys inaccessible. + cache: VecDeque, +} + +impl BootLevelKeyCache { + const HKDF_ADVANCE: &'static [u8] = b"Advance KDF one step"; + const HKDF_AES: &'static [u8] = b"Generate AES-256-GCM key"; + const HKDF_KEY_SIZE: usize = 32; + + /// Initialize the cache with the level zero key. + pub fn new(level_zero_key: ZVec) -> Self { + let mut cache: VecDeque = VecDeque::new(); + cache.push_back(level_zero_key); + Self { current: 0, cache } + } + + /// Report whether the key for the given level can be inferred. + pub fn level_accessible(&self, boot_level: usize) -> bool { + // If the requested boot level is lower than the current boot level + // or if we have reached the end (`cache.empty()`) we can't retrieve + // the boot key. + boot_level >= self.current && !self.cache.is_empty() + } + + /// Get the HKDF key for boot level `boot_level`. The key for level *i*+1 + /// is calculated from the level *i* key using `hkdf_expand`. + fn get_hkdf_key(&mut self, boot_level: usize) -> Result> { + if !self.level_accessible(boot_level) { + return Ok(None); + } + // `self.cache.len()` represents the first entry not in the cache, + // so `self.current + self.cache.len()` is the first boot level not in the cache. + let first_not_cached = self.current + self.cache.len(); + + // Grow the cache forwards until it contains the desired boot level. + for _level in first_not_cached..=boot_level { + // We check at the start that cache is non-empty and future iterations only push, + // so this must unwrap. + let highest_key = self.cache.back().unwrap(); + let next_key = hkdf_expand(Self::HKDF_KEY_SIZE, highest_key, Self::HKDF_ADVANCE) + .context(err!("Advancing key one step"))?; + self.cache.push_back(next_key); + } + + // If we reach this point, we should have a key at index boot_level - current. + Ok(Some(self.cache.get(boot_level - self.current).unwrap())) + } + + /// Drop keys prior to the given boot level, while retaining the ability to generate keys for + /// that level and later. + pub fn advance_boot_level(&mut self, new_boot_level: usize) -> Result<()> { + if !self.level_accessible(new_boot_level) { + log::error!( + "Failed to advance boot level to {}, current is {}, cache size {}", + new_boot_level, + self.current, + self.cache.len() + ); + return Ok(()); + } + + // We `get` the new boot level for the side effect of advancing the cache to a point + // where the new boot level is present. + self.get_hkdf_key(new_boot_level) + .context(err!("Advancing cache"))?; + + // Then we split the queue at the index of the new boot level and discard the front, + // keeping only the keys with the current boot level or higher. + self.cache = self.cache.split_off(new_boot_level - self.current); + + // The new cache has the new boot level at index 0, so we set `current` to + // `new_boot_level`. + self.current = new_boot_level; + + Ok(()) + } + + /// Drop all keys, effectively raising the current boot level to infinity; no keys can + /// be inferred from this point on. + pub fn finish(&mut self) { + self.cache.clear(); + } + + fn expand_key( + &mut self, + boot_level: usize, + out_len: usize, + info: &[u8], + ) -> Result> { + self.get_hkdf_key(boot_level) + .context(err!("Looking up HKDF key"))? + .map(|k| hkdf_expand(out_len, k, info)) + .transpose() + .context(err!("Calling hkdf_expand")) + } + + /// Return the AES-256-GCM key for the current boot level. + pub fn aes_key(&mut self, boot_level: usize) -> Result> { + self.expand_key(boot_level, AES_256_KEY_LENGTH, BootLevelKeyCache::HKDF_AES) + .context(err!("expand_key failed")) + } +} diff --git a/libs/rust/src/keymaster/crypto.rs b/libs/rust/src/keymaster/crypto.rs new file mode 100644 index 0000000..cbc0e85 --- /dev/null +++ b/libs/rust/src/keymaster/crypto.rs @@ -0,0 +1,100 @@ +use anyhow::{Context, Result}; +use kmr_crypto_boring::{km::*, zvec::ZVec}; + +use crate::err; + +pub struct ECDHPrivateKey(ECKey); + +impl ECDHPrivateKey { + /// Randomly generate a fresh keypair. + pub fn generate() -> Result { + ec_key_generate_key() + .map(ECDHPrivateKey) + .context(err!("generation failed")) + } + + /// Deserialize bytes into an ECDH keypair + pub fn from_private_key(buf: &[u8]) -> Result { + ec_key_parse_private_key(buf) + .map(ECDHPrivateKey) + .context(err!("parsing failed")) + } + + /// Serialize the ECDH key into bytes + pub fn private_key(&self) -> Result { + ec_key_marshal_private_key(&self.0).context(err!("marshalling failed")) + } + + /// Generate the serialization of the corresponding public key + pub fn public_key(&self) -> Result> { + let point = ec_key_get0_public_key(&self.0); + ec_point_point_to_oct(point.get_point()).context(err!("marshalling failed")) + } + + /// Use ECDH to agree an AES key with another party whose public key we have. + /// Sender and recipient public keys are passed separately because they are + /// switched in encryption vs decryption. + fn agree_key( + &self, + salt: &[u8], + other_public_key: &[u8], + sender_public_key: &[u8], + recipient_public_key: &[u8], + ) -> Result { + let hkdf = hkdf_extract(sender_public_key, salt) + .context(err!("hkdf_extract on sender_public_key failed"))?; + let hkdf = hkdf_extract(recipient_public_key, &hkdf) + .context(err!("hkdf_extract on recipient_public_key failed"))?; + let other_public_key = ec_point_oct_to_point(other_public_key) + .context(err!("ec_point_oct_to_point failed"))?; + let secret = ecdh_compute_key(other_public_key.get_point(), &self.0) + .context(err!("ecdh_compute_key failed"))?; + let prk = hkdf_extract(&secret, &hkdf).context(err!("hkdf_extract on secret failed"))?; + + let aes_key = hkdf_expand(AES_256_KEY_LENGTH, &prk, b"AES-256-GCM key") + .context(err!("hkdf_expand failed"))?; + Ok(aes_key) + } + + /// Encrypt a message to the party with the given public key + pub fn encrypt_message( + recipient_public_key: &[u8], + message: &[u8], + ) -> Result<(Vec, Vec, Vec, Vec, Vec)> { + let sender_key = Self::generate().context(err!("generate failed"))?; + let sender_public_key = sender_key.public_key().context(err!("public_key failed"))?; + let salt = generate_salt().context(err!("generate_salt failed"))?; + let aes_key = sender_key + .agree_key( + &salt, + recipient_public_key, + &sender_public_key, + recipient_public_key, + ) + .context(err!("agree_key failed"))?; + let (ciphertext, iv, tag) = + aes_gcm_encrypt(message, &aes_key).context(err!("aes_gcm_encrypt failed"))?; + Ok((sender_public_key, salt, iv, ciphertext, tag)) + } + + /// Decrypt a message sent to us + pub fn decrypt_message( + &self, + sender_public_key: &[u8], + salt: &[u8], + iv: &[u8], + ciphertext: &[u8], + tag: &[u8], + ) -> Result { + let recipient_public_key = self.public_key()?; + let aes_key = self + .agree_key( + salt, + sender_public_key, + sender_public_key, + &recipient_public_key, + ) + .context(err!("agree_key failed"))?; + aes_gcm_decrypt(ciphertext, iv, tag, &aes_key).context(err!("aes_gcm_decrypt failed")) + } +} diff --git a/libs/rust/src/keymaster/database/mod.rs b/libs/rust/src/keymaster/database/mod.rs index 1a2c959..cb9867f 100644 --- a/libs/rust/src/keymaster/database/mod.rs +++ b/libs/rust/src/keymaster/database/mod.rs @@ -1 +1,2 @@ -pub mod perboot; \ No newline at end of file +pub mod perboot; +pub mod utils; diff --git a/libs/rust/src/keymaster/database/perboot.rs b/libs/rust/src/keymaster/database/perboot.rs index 060c0ab..7ebb2b7 100644 --- a/libs/rust/src/keymaster/database/perboot.rs +++ b/libs/rust/src/keymaster/database/perboot.rs @@ -82,7 +82,10 @@ impl PerbootDB { /// Add a new auth token + timestamp to the database, replacing any which /// match all of user_id, auth_id, and auth_type. pub fn insert_auth_token_entry(&self, entry: AuthTokenEntry) { - self.auth_tokens.write().unwrap().replace(AuthTokenEntryWrap(entry)); + self.auth_tokens + .write() + .unwrap() + .replace(AuthTokenEntryWrap(entry)); } /// Locate an auth token entry which matches the predicate with the most /// recent update time. @@ -102,6 +105,12 @@ impl PerbootDB { #[cfg(test)] /// For testing, return all auth tokens currently tracked. pub fn get_all_auth_token_entries(&self) -> Vec { - self.auth_tokens.read().unwrap().iter().cloned().map(|x| x.0).collect() + self.auth_tokens + .read() + .unwrap() + .iter() + .cloned() + .map(|x| x.0) + .collect() } } diff --git a/libs/rust/src/keymaster/database/utils.rs b/libs/rust/src/keymaster/database/utils.rs new file mode 100644 index 0000000..f2c467b --- /dev/null +++ b/libs/rust/src/keymaster/database/utils.rs @@ -0,0 +1,233 @@ +use anyhow::{Context, Result}; +use rusqlite::{types::FromSql, Row, Rows}; + +use crate::keymaster::error::KsError; + +// Takes Rows as returned by a query call on prepared statement. +// Extracts exactly one row with the `row_extractor` and fails if more +// rows are available. +// If no row was found, `None` is passed to the `row_extractor`. +// This allows the row extractor to decide on an error condition or +// a different default behavior. +pub fn with_rows_extract_one<'a, T, F>(rows: &mut Rows<'a>, row_extractor: F) -> Result +where + F: FnOnce(Option<&Row<'a>>) -> Result, +{ + let result = row_extractor( + rows.next() + .context("with_rows_extract_one: Failed to unpack row.")?, + ); + + rows.next() + .context("In with_rows_extract_one: Failed to unpack unexpected row.")? + .map_or_else(|| Ok(()), |_| Err(KsError::sys())) + .context("In with_rows_extract_one: Unexpected row.")?; + + result +} + +pub fn with_rows_extract_all<'a, F>(rows: &mut Rows<'a>, mut row_extractor: F) -> Result<()> +where + F: FnMut(&Row<'a>) -> Result<()>, +{ + loop { + match rows + .next() + .context("In with_rows_extract_all: Failed to unpack row")? + { + Some(row) => { + row_extractor(row).context("In with_rows_extract_all.")?; + } + None => break Ok(()), + } + } +} + +/// This struct is defined to postpone converting rusqlite column value to the +/// appropriate key parameter value until we know the corresponding tag value. +/// Wraps the column index and a rusqlite row. +pub struct SqlField<'a>(usize, &'a Row<'a>); + +impl<'a> SqlField<'a> { + /// Creates a new SqlField with the given index and row. + pub fn new(index: usize, row: &'a Row<'a>) -> Self { + Self(index, row) + } + /// Returns the column value from the row, when we know the expected type. + pub fn get(&self) -> rusqlite::Result { + self.1.get(self.0) + } +} + +/// This macro implements two types to aid in the implementation of a type safe metadata +/// store. The first is a collection of metadata and the second is the entry in that +/// collection. The caller has to provide the infrastructure to load and store the +/// the collection or individual entries in a SQLite database. The idea is that once +/// the infrastructure for a metadata collection is in place all it takes to add another +/// field is make a new entry in the list of variants (see details below). +/// +/// # Usage +/// ``` +/// impl_metadata!{ +/// /// This is the name of the collection. +/// #[derive(Debug, Default)] +/// pub struct CollectionName; +/// /// This is the name of the Entry type followed by a list of variants, accessor function +/// /// names, and types. +/// #[derive(Debug, Eq, PartialEq)] +/// pub enum EntryName { +/// /// An enum variant with an accessor function name. +/// VariantA(u32) with accessor get_variant_a, +/// /// A second variant. `MyType` must implement rusqlite::types::ToSql and FromSql. +/// VariantB(MyType) with accessor get_variant_b, +/// // --- ADD NEW META DATA FIELDS HERE --- +/// // For backwards compatibility add new entries only to +/// // end of this list and above this comment. +/// }; +/// } +/// ``` +/// +/// expands to: +/// +/// ``` +/// pub enum EntryName { +/// VariantA(u32), +/// VariantB(MyType), +/// } +/// +/// impl EntryName {} +/// /// Returns a numeric variant id that can be used for persistent storage. +/// fn db_tag(&self) -> i64 {...} +/// /// Helper function that constructs a new `EntryName` given a variant identifier +/// /// and a to-be-extracted `SqlFiled` +/// fn new_from_sql(db_tag: i64, data: &SqlField) -> Result {...} +/// } +/// +/// impl ToSql for EntryName {...} +/// +/// pub struct CollectionName { +/// data: std::collections::HashMap, +/// } +/// +/// impl CollectionName { +/// /// Create a new collection of meta data. +/// pub fn new() -> Self {...} +/// /// Add a new entry to this collection. Replaces existing entries of the +/// /// same variant unconditionally. +/// pub fn add(&mut self, e: EntryName) {...} +/// /// Type safe accessor function for the defined fields. +/// pub fn get_variant_a() -> Option {...} +/// pub fn get_variant_b() -> Option {...} +/// } +/// +/// let mut collection = CollectionName::new(); +/// collection.add(EntryName::VariantA(3)); +/// let three: u32 = collection.get_variant_a().unwrap() +/// ``` +/// +/// The caller of this macro must implement the actual database queries to load and store +/// either a whole collection of metadata or individual fields. For example by associating +/// with the given type: +/// ``` +/// impl CollectionName { +/// fn load(tx: &Transaction) -> Result {...} +/// } +/// ``` +#[macro_export] +macro_rules! impl_metadata { + // These two macros assign incrementing numeric ids to each field which are used as + // database tags. + (@gen_consts {} {$($n:ident $nid:tt,)*} {$($count:tt)*}) => { + $( + // This allows us to reuse the variant name for these constants. The constants + // are private so that this exception does not spoil the public interface. + #[allow(non_upper_case_globals)] + const $n: i64 = $nid; + )* + }; + (@gen_consts {$first:ident $(,$tail:ident)*} {$($out:tt)*} {$($count:tt)*}) => { + impl_metadata!(@gen_consts {$($tail),*} {$($out)* $first ($($count)*),} {$($count)* + 1}); + }; + ( + $(#[$nmeta:meta])* + $nvis:vis struct $name:ident; + $(#[$emeta:meta])* + $evis:vis enum $entry:ident { + $($(#[$imeta:meta])* $vname:ident($t:ty) with accessor $func:ident),* $(,)? + }; + ) => { + $(#[$emeta])* + $evis enum $entry { + $( + $(#[$imeta])* + $vname($t), + )* + } + + impl $entry { + fn db_tag(&self) -> i64 { + match self { + $(Self::$vname(_) => $name::$vname,)* + } + } + + fn new_from_sql(db_tag: i64, data: &SqlField) -> anyhow::Result { + match db_tag { + $( + $name::$vname => { + Ok($entry::$vname( + data.get() + .with_context(|| format!( + "In {}::new_from_sql: Unable to get {}.", + stringify!($entry), + stringify!($vname) + ))? + )) + }, + )* + _ => Err(anyhow!(format!( + "In {}::new_from_sql: unknown db tag {}.", + stringify!($entry), db_tag + ))), + } + } + } + + impl rusqlite::types::ToSql for $entry { + fn to_sql(&self) -> rusqlite::Result { + match self { + $($entry::$vname(v) => v.to_sql(),)* + } + } + } + + $(#[$nmeta])* + $nvis struct $name { + data: std::collections::HashMap, + } + + impl $name { + /// Create a new instance of $name + pub fn new() -> Self { + Self{data: std::collections::HashMap::new()} + } + + impl_metadata!{@gen_consts {$($vname),*} {} {0}} + + /// Add a new instance of $entry to this collection of metadata. + pub fn add(&mut self, entry: $entry) { + self.data.insert(entry.db_tag(), entry); + } + $( + /// If the variant $vname is set, returns the wrapped value or None otherwise. + pub fn $func(&self) -> Option<&$t> { + if let Some($entry::$vname(v)) = self.data.get(&Self::$vname) { + Some(v) + } else { + None + } + } + )* + } + }; +} diff --git a/libs/rust/src/keymaster/db.rs b/libs/rust/src/keymaster/db.rs index 5b376a5..cab4886 100644 --- a/libs/rust/src/keymaster/db.rs +++ b/libs/rust/src/keymaster/db.rs @@ -1,9 +1,27 @@ -use std::{path::Path, time::Duration}; +use std::{ + collections::{HashMap, HashSet}, + ops::Deref, + path::Path, + sync::{Arc, Condvar, LazyLock, Mutex}, + time::{Duration, SystemTime, SystemTimeError}, +}; -use anyhow::{Context, Result}; -use rusqlite::{params, types::{FromSql, FromSqlResult, ToSqlOutput, Value, ValueRef}, Connection, ToSql, Transaction}; +use anyhow::{anyhow, Context, Result}; +use rand::random; +use rusqlite::{ + params, + types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef}, + Connection, OptionalExtension, ToSql, Transaction, +}; -use crate::{android::hardware::security::keymint::{HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType}, utils::get_current_time_in_milliseconds}; +use crate::{ + android::hardware::security::keymint::ErrorCode::ErrorCode, + keymaster::{database::perboot::PerbootDB, super_key::SuperKeyType}, + plat::utils::AID_USER_OFFSET, + watchdog as wd, +}; + +use TransactionBehavior::Immediate; const DB_ROOT_PATH: &str = "./omk/data"; @@ -14,27 +32,83 @@ const PERSISTENT_DB_FILENAME: &'static str = "keymaster.db"; const DB_BUSY_RETRY_INTERVAL: Duration = Duration::from_micros(500); +/// Wrapper for `rusqlite::TransactionBehavior` which includes information about the transaction +/// being performed. +#[derive(Clone, Copy)] +enum TransactionBehavior { + Deferred, + Immediate(&'static str), +} + +impl From for rusqlite::TransactionBehavior { + fn from(val: TransactionBehavior) -> Self { + match val { + TransactionBehavior::Deferred => rusqlite::TransactionBehavior::Deferred, + TransactionBehavior::Immediate(_) => rusqlite::TransactionBehavior::Immediate, + } + } +} + +impl TransactionBehavior { + fn name(&self) -> Option<&'static str> { + match self { + TransactionBehavior::Deferred => None, + TransactionBehavior::Immediate(v) => Some(v), + } + } +} + +use crate::{ + android::{ + hardware::security::keymint::{ + HardwareAuthToken::HardwareAuthToken, + HardwareAuthenticatorType::HardwareAuthenticatorType, + KeyParameter::KeyParameter as KmKeyParameter, SecurityLevel::SecurityLevel, Tag::Tag, + }, + system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, + }, + }, + err, impl_metadata, + keymaster::{ + database::utils::{self, SqlField}, + error::KsError, + key_parameter::KeyParameter, + permission::KeyPermSet, + }, + utils::get_current_time_in_milliseconds, + watchdog, +}; + pub struct KeymasterDb { conn: Connection, + perboot: Arc, } impl KeymasterDb { + const UNASSIGNED_KEY_ID: i64 = -1i64; + pub fn new() -> Result { + let _wp = wd::watch("KeystoreDB::new"); + let path = format!("{}/{}", DB_ROOT_PATH, PERSISTENT_DB_FILENAME); if let Some(p) = Path::new(&path).parent() { std::fs::create_dir_all(p).context("Failed to create directory for database.")?; } let mut conn = Self::make_connection(&path)?; - let init_table = conn - .transaction() - .context("Failed to create transaction for initializing database.")?; + let init_table = conn.transaction().context(err!( + "Failed to create transaction for initializing database." + ))?; Self::init_tables(&init_table)?; - init_table - .commit() - .context("Failed to commit transaction for initializing database.")?; + init_table.commit().context(err!( + "Failed to commit transaction for initializing database." + ))?; - Ok(KeymasterDb { conn }) + Ok(KeymasterDb { + conn, + perboot: crate::keymaster::database::perboot::PERBOOT_DB.clone(), + }) } pub fn connection(&self) -> &Connection { @@ -60,21 +134,23 @@ impl KeymasterDb { km_uuid BLOB);", [], ) - .context("Failed to initialize \"keyentry\" table.")?; + .context(err!("Failed to initialize \"keyentry\" table."))?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keyentry_id_index ON keyentry(id);", [], ) - .context("Failed to create index keyentry_id_index.")?; + .context(err!("Failed to create index keyentry_id_index."))?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keyentry_domain_namespace_index ON keyentry(domain, namespace, alias);", [], ) - .context("Failed to create index keyentry_domain_namespace_index.")?; + .context(err!( + "Failed to create index keyentry_domain_namespace_index." + ))?; // Index added in v2 of database schema. tx.execute( @@ -82,7 +158,7 @@ impl KeymasterDb { ON keyentry(state);", [], ) - .context("Failed to create index keyentry_state_index.")?; + .context(err!("Failed to create index keyentry_state_index."))?; tx.execute( "CREATE TABLE IF NOT EXISTS persistent.blobentry ( @@ -93,14 +169,14 @@ impl KeymasterDb { state INTEGER DEFAULT 0);", // `state` added in v2 of schema [], ) - .context("Failed to initialize \"blobentry\" table.")?; + .context(err!("Failed to initialize \"blobentry\" table."))?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.blobentry_keyentryid_index ON blobentry(keyentryid);", [], ) - .context("Failed to create index blobentry_keyentryid_index.")?; + .context(err!("Failed to create index blobentry_keyentryid_index."))?; // Index added in v2 of database schema. tx.execute( @@ -108,7 +184,7 @@ impl KeymasterDb { ON blobentry(subcomponent_type, state);", [], ) - .context("Failed to create index blobentry_state_index.")?; + .context(err!("Failed to create index blobentry_state_index."))?; tx.execute( "CREATE TABLE IF NOT EXISTS persistent.blobmetadata ( @@ -119,14 +195,16 @@ impl KeymasterDb { UNIQUE (blobentryid, tag));", [], ) - .context("Failed to initialize \"blobmetadata\" table.")?; + .context(err!("Failed to initialize \"blobmetadata\" table."))?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.blobmetadata_blobentryid_index ON blobmetadata(blobentryid);", [], ) - .context("Failed to create index blobmetadata_blobentryid_index.")?; + .context(err!( + "Failed to create index blobmetadata_blobentryid_index." + ))?; tx.execute( "CREATE TABLE IF NOT EXISTS persistent.keyparameter ( @@ -136,14 +214,16 @@ impl KeymasterDb { security_level INTEGER);", [], ) - .context("Failed to initialize \"keyparameter\" table.")?; + .context(err!("Failed to initialize \"keyparameter\" table."))?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keyparameter_keyentryid_index ON keyparameter(keyentryid);", [], ) - .context("Failed to create index keyparameter_keyentryid_index.")?; + .context(err!( + "Failed to create index keyparameter_keyentryid_index." + ))?; tx.execute( "CREATE TABLE IF NOT EXISTS persistent.keymetadata ( @@ -153,14 +233,14 @@ impl KeymasterDb { UNIQUE (keyentryid, tag));", [], ) - .context("Failed to initialize \"keymetadata\" table.")?; + .context(err!("Failed to initialize \"keymetadata\" table."))?; tx.execute( "CREATE INDEX IF NOT EXISTS persistent.keymetadata_keyentryid_index ON keymetadata(keyentryid);", [], ) - .context("Failed to create index keymetadata_keyentryid_index.")?; + .context(err!("Failed to create index keymetadata_keyentryid_index."))?; tx.execute( "CREATE TABLE IF NOT EXISTS persistent.grant ( @@ -170,7 +250,7 @@ impl KeymasterDb { access_vector INTEGER);", [], ) - .context("Failed to initialize \"grant\" table.")?; + .context(err!("Failed to initialize \"grant\" table."))?; Ok(()) } @@ -182,7 +262,7 @@ impl KeymasterDb { loop { if let Err(e) = conn .execute("ATTACH DATABASE ? as persistent;", params![persistent_file]) - .context("Failed to attach database persistent.") + .context(err!("Failed to attach database persistent.")) { if Self::is_locked_error(&e) { std::thread::sleep(DB_BUSY_RETRY_INTERVAL); @@ -196,11 +276,63 @@ impl KeymasterDb { // Drop the cache size from default (2M) to 0.5M conn.execute("PRAGMA persistent.cache_size = -500;", params![]) - .context("Failed to decrease cache size for persistent db")?; + .context(err!("Failed to decrease cache size for persistent db"))?; Ok(conn) } + /// Creates a transaction with the given behavior and executes f with the new transaction. + /// The transaction is committed only if f returns Ok and retried if DatabaseBusy + /// or DatabaseLocked is encountered. + fn with_transaction(&mut self, behavior: TransactionBehavior, f: F) -> Result + where + F: Fn(&Transaction) -> Result, + { + let name = behavior.name().unwrap_or(""); + loop { + let result = self + .conn + .transaction_with_behavior(behavior.into()) + .context("Failed to create transaction.") + .and_then(|tx| { + let value = f(&tx)?; + tx.commit() + .context(err!("Failed to commit transaction: {}", name))?; + Ok(value) + }); + match result { + Ok(result) => break Ok(result), + Err(e) => { + if Self::is_locked_error(&e) { + std::thread::sleep(DB_BUSY_RETRY_INTERVAL); + continue; + } else { + return Err(e).context(err!("Failed to process transaction: {}", name)); + } + } + } + } + } + + /// This maintenance function should be called only once before the database is used for the + /// first time. It restores the invariant that `KeyLifeCycle::Existing` is a transient state. + /// The function transitions all key entries from Existing to Unreferenced unconditionally and + /// returns the number of rows affected. If this returns a value greater than 0, it means that + /// Keystore crashed at some point during key generation. Callers may want to log such + /// occurrences. + /// Unlike with `mark_unreferenced`, we don't need to purge grants, because only keys that made + /// it to `KeyLifeCycle::Live` may have grants. + pub fn cleanup_leftovers(&mut self) -> Result { + self.with_transaction(Immediate("TX_cleanup_leftovers"), |tx| { + tx.execute( + "UPDATE persistent.keyentry SET state = ? WHERE state = ?;", + params![KeyLifeCycle::Unreferenced, KeyLifeCycle::Existing], + ) + .context("Failed to execute query.") + }) + .context("Failed to cleanup leftovers.") + } + fn is_locked_error(e: &anyhow::Error) -> bool { matches!( e.root_cause().downcast_ref::(), @@ -213,6 +345,947 @@ impl KeymasterDb { }) ) } + + fn mark_unreferenced(tx: &Transaction, key_id: i64) -> Result { + let updated = tx + .execute( + "DELETE FROM persistent.keyentry WHERE id = ?;", + params![key_id], + ) + .context("Trying to delete keyentry.")?; + tx.execute( + "DELETE FROM persistent.keymetadata WHERE keyentryid = ?;", + params![key_id], + ) + .context("Trying to delete keymetadata.")?; + tx.execute( + "DELETE FROM persistent.keyparameter WHERE keyentryid = ?;", + params![key_id], + ) + .context("Trying to delete keyparameters.")?; + tx.execute( + "DELETE FROM persistent.grant WHERE keyentryid = ?;", + params![key_id], + ) + .context("Trying to delete grants to other apps.")?; + // The associated blobentry rows are not immediately deleted when the owning keyentry is + // removed, because a KeyMint `deleteKey()` invocation is needed (specifically for the + // `KEY_BLOB`). That should not be done from within the database transaction. Also, calls + // to `deleteKey()` need to be delayed until the boot has completed, to avoid making + // permanent changes during an OTA before the point of no return. Mark the affected rows + // with `state=Orphaned` so a subsequent garbage collection can do the `deleteKey()`. + tx.execute( + "UPDATE persistent.blobentry SET state = ? WHERE keyentryid = ?", + params![BlobState::Orphaned, key_id], + ) + .context("Trying to mark blobentrys as superseded")?; + Ok(updated != 0) + } + + /// This helper function completes the access tuple of a key, which is required + /// to perform access control. The strategy depends on the `domain` field in the + /// key descriptor. + /// * Domain::SELINUX: The access tuple is complete and this function only loads + /// the key_id for further processing. + /// * Domain::APP: Like Domain::SELINUX, but the tuple is completed by `caller_uid` + /// which serves as the namespace. + /// * Domain::GRANT: The grant table is queried for the `key_id` and the + /// `access_vector`. + /// * Domain::KEY_ID: The keyentry table is queried for the owning `domain` and + /// `namespace`. + /// + /// In each case the information returned is sufficient to perform the access + /// check and the key id can be used to load further key artifacts. + fn load_access_tuple( + tx: &Transaction, + key: &KeyDescriptor, + key_type: KeyType, + caller_uid: u32, + ) -> Result { + match key.domain { + // Domain App or SELinux. In this case we load the key_id from + // the keyentry database for further loading of key components. + // We already have the full access tuple to perform access control. + // The only distinction is that we use the caller_uid instead + // of the caller supplied namespace if the domain field is + // Domain::APP. + Domain::APP | Domain::SELINUX => { + let mut access_key = key.clone(); + if access_key.domain == Domain::APP { + access_key.nspace = caller_uid as i64; + } + let key_id = Self::load_key_entry_id(tx, &access_key, key_type) + .with_context(|| format!("With key.domain = {:?}.", access_key.domain))?; + + Ok(KeyAccessInfo { + key_id, + descriptor: access_key, + vector: None, + }) + } + + // Domain::GRANT. In this case we load the key_id and the access_vector + // from the grant table. + Domain::GRANT => { + let mut stmt = tx + .prepare( + "SELECT keyentryid, access_vector FROM persistent.grant + WHERE grantee = ? AND id = ? AND + (SELECT state FROM persistent.keyentry WHERE id = keyentryid) = ?;", + ) + .context("Domain::GRANT prepare statement failed")?; + let mut rows = stmt + .query(params![caller_uid as i64, key.nspace, KeyLifeCycle::Live]) + .context("Domain:Grant: query failed.")?; + let (key_id, access_vector): (i64, i32) = + utils::with_rows_extract_one(&mut rows, |row| { + let r = + row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)?; + Ok(( + r.get(0).context("Failed to unpack key_id.")?, + r.get(1).context("Failed to unpack access_vector.")?, + )) + }) + .context("Domain::GRANT.")?; + Ok(KeyAccessInfo { + key_id, + descriptor: key.clone(), + vector: Some(access_vector.into()), + }) + } + + // Domain::KEY_ID. In this case we load the domain and namespace from the + // keyentry database because we need them for access control. + Domain::KEY_ID => { + let (domain, namespace): (Domain, i64) = { + let mut stmt = tx + .prepare( + "SELECT domain, namespace FROM persistent.keyentry + WHERE + id = ? + AND state = ?;", + ) + .context("Domain::KEY_ID: prepare statement failed")?; + let mut rows = stmt + .query(params![key.nspace, KeyLifeCycle::Live]) + .context("Domain::KEY_ID: query failed.")?; + utils::with_rows_extract_one(&mut rows, |row| { + let r = + row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)?; + Ok(( + Domain(r.get(0).context("Failed to unpack domain.")?), + r.get(1).context("Failed to unpack namespace.")?, + )) + }) + .context("Domain::KEY_ID.")? + }; + + // We may use a key by id after loading it by grant. + // In this case we have to check if the caller has a grant for this particular + // key. We can skip this if we already know that the caller is the owner. + // But we cannot know this if domain is anything but App. E.g. in the case + // of Domain::SELINUX we have to speculatively check for grants because we have to + // consult the SEPolicy before we know if the caller is the owner. + let access_vector: Option = + if domain != Domain::APP || namespace != caller_uid as i64 { + let access_vector: Option = tx + .query_row( + "SELECT access_vector FROM persistent.grant + WHERE grantee = ? AND keyentryid = ?;", + params![caller_uid as i64, key.nspace], + |row| row.get(0), + ) + .optional() + .context("Domain::KEY_ID: query grant failed.")?; + access_vector.map(|p| p.into()) + } else { + None + }; + + let key_id = key.nspace; + let mut access_key: KeyDescriptor = key.clone(); + access_key.domain = domain; + access_key.nspace = namespace; + + Ok(KeyAccessInfo { + key_id, + descriptor: access_key, + vector: access_vector, + }) + } + _ => Err(anyhow!(KsError::Rc(ResponseCode::INVALID_ARGUMENT))), + } + } + + // Helper function loading the key_id given the key descriptor + // tuple comprising domain, namespace, and alias. + // Requires a valid transaction. + fn load_key_entry_id(tx: &Transaction, key: &KeyDescriptor, key_type: KeyType) -> Result { + let alias = key + .alias + .as_ref() + .map_or_else(|| Err(KsError::sys()), Ok) + .context(err!("In load_key_entry_id: Alias must be specified."))?; + let mut stmt = tx + .prepare( + "SELECT id FROM persistent.keyentry + WHERE + key_type = ? + AND domain = ? + AND namespace = ? + AND alias = ? + AND state = ?;", + ) + .context(err!( + "In load_key_entry_id: Failed to select from keyentry table." + ))?; + let mut rows = stmt + .query(params![ + key_type, + key.domain.0 as u32, + key.nspace, + alias, + KeyLifeCycle::Live + ]) + .context(err!( + "In load_key_entry_id: Failed to read from keyentry table." + ))?; + utils::with_rows_extract_one(&mut rows, |row| { + row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)? + .get(0) + .context("Failed to unpack id.") + }) + .context(err!("In load_key_entry_id: Failed to load key entry id.")) + } + + /// Checks if a key exists with given key type and key descriptor properties. + pub fn key_exists( + &mut self, + domain: Domain, + nspace: i64, + alias: &str, + key_type: KeyType, + ) -> anyhow::Result { + self.with_transaction(Immediate("TX_key_exists"), |tx| { + let key_descriptor = KeyDescriptor { + domain, + nspace, + alias: Some(alias.to_string()), + blob: None, + }; + let result = Self::load_key_entry_id(tx, &key_descriptor, key_type); + match result { + Ok(_) => Ok(true), + Err(error) => match error.root_cause().downcast_ref::() { + Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(false), + _ => Err(error).context(err!("Failed to find if the key exists.")), + }, + } + }) + .context(err!("Failed to check if key exists.")) + } + + fn get_key_km_uuid(tx: &Transaction, key_id: i64) -> Result { + tx.query_row( + "SELECT km_uuid FROM persistent.keyentry WHERE id = ?", + params![key_id], + |row| row.get(0), + ) + .context(err!()) + } + + fn load_key_components( + tx: &Transaction, + load_bits: KeyEntryLoadBits, + key_id: i64, + ) -> Result { + let metadata = KeyMetaData::load_from_db(key_id, tx).context("In load_key_components.")?; + + let (has_km_blob, key_blob_info, cert_blob, cert_chain_blob) = + Self::load_blob_components(key_id, load_bits, tx).context("In load_key_components.")?; + + let parameters = Self::load_key_parameters(key_id, tx) + .context("In load_key_components: Trying to load key parameters.")?; + + let km_uuid = Self::get_key_km_uuid(tx, key_id) + .context("In load_key_components: Trying to get KM uuid.")?; + + Ok(KeyEntry { + id: key_id, + key_blob_info, + cert: cert_blob, + cert_chain: cert_chain_blob, + km_uuid, + parameters, + metadata, + pure_cert: !has_km_blob, + }) + } + + fn load_blob_components( + key_id: i64, + load_bits: KeyEntryLoadBits, + tx: &Transaction, + ) -> Result<( + bool, + Option<(Vec, BlobMetaData)>, + Option>, + Option>, + )> { + let mut stmt = tx + .prepare( + "SELECT MAX(id), subcomponent_type, blob FROM persistent.blobentry + WHERE keyentryid = ? GROUP BY subcomponent_type;", + ) + .context(err!("prepare statement failed."))?; + + let mut rows = stmt.query(params![key_id]).context(err!("query failed."))?; + + let mut key_blob: Option<(i64, Vec)> = None; + let mut cert_blob: Option> = None; + let mut cert_chain_blob: Option> = None; + let mut has_km_blob: bool = false; + utils::with_rows_extract_all(&mut rows, |row| { + let sub_type: SubComponentType = + row.get(1).context("Failed to extract subcomponent_type.")?; + has_km_blob = has_km_blob || sub_type == SubComponentType::KEY_BLOB; + match (sub_type, load_bits.load_public(), load_bits.load_km()) { + (SubComponentType::KEY_BLOB, _, true) => { + key_blob = Some(( + row.get(0).context("Failed to extract key blob id.")?, + row.get(2).context("Failed to extract key blob.")?, + )); + } + (SubComponentType::CERT, true, _) => { + cert_blob = Some( + row.get(2) + .context("Failed to extract public certificate blob.")?, + ); + } + (SubComponentType::CERT_CHAIN, true, _) => { + cert_chain_blob = Some( + row.get(2) + .context("Failed to extract certificate chain blob.")?, + ); + } + (SubComponentType::CERT, _, _) + | (SubComponentType::CERT_CHAIN, _, _) + | (SubComponentType::KEY_BLOB, _, _) => {} + _ => Err(KsError::sys()).context("Unknown subcomponent type.")?, + } + Ok(()) + }) + .context(err!())?; + + let blob_info = key_blob.map_or::, _>(Ok(None), |(blob_id, blob)| { + Ok(Some(( + blob, + BlobMetaData::load_from_db(blob_id, tx) + .context(err!("Trying to load blob_metadata."))?, + ))) + })?; + + Ok((has_km_blob, blob_info, cert_blob, cert_chain_blob)) + } + + fn load_key_parameters(key_id: i64, tx: &Transaction) -> Result> { + let mut stmt = tx + .prepare( + "SELECT tag, data, security_level from persistent.keyparameter + WHERE keyentryid = ?;", + ) + .context("In load_key_parameters: prepare statement failed.")?; + + let mut parameters: Vec = Vec::new(); + + let mut rows = stmt + .query(params![key_id]) + .context("In load_key_parameters: query failed.")?; + utils::with_rows_extract_all(&mut rows, |row| { + let tag = Tag(row.get(0).context("Failed to read tag.")?); + let sec_level = SecurityLevel(row.get(2).context("Failed to read sec_level.")?); + parameters.push( + KeyParameter::new_from_sql(tag, &SqlField::new(1, row), sec_level) + .context("Failed to read KeyParameter.")?, + ); + Ok(()) + }) + .context(err!())?; + + Ok(parameters) + } + + /// Load a key entry by the given key descriptor. + /// It uses the `check_permission` callback to verify if the access is allowed + /// given the key access tuple read from the database using `load_access_tuple`. + /// With `load_bits` the caller may specify which blobs shall be loaded from + /// the blob database. + pub fn load_key_entry( + &mut self, + key: &KeyDescriptor, + key_type: KeyType, + load_bits: KeyEntryLoadBits, + caller_uid: u32, + ) -> Result<(KeyIdGuard, KeyEntry)> { + loop { + match self.load_key_entry_internal(key, key_type, load_bits, caller_uid) { + Ok(result) => break Ok(result), + Err(e) => { + if Self::is_locked_error(&e) { + std::thread::sleep(DB_BUSY_RETRY_INTERVAL); + continue; + } else { + return Err(e).context(err!()); + } + } + } + } + } + + fn load_key_entry_internal( + &mut self, + key: &KeyDescriptor, + key_type: KeyType, + load_bits: KeyEntryLoadBits, + caller_uid: u32, + ) -> Result<(KeyIdGuard, KeyEntry)> { + // KEY ID LOCK 1/2 + // If we got a key descriptor with a key id we can get the lock right away. + // Otherwise we have to defer it until we know the key id. + let key_id_guard = match key.domain { + Domain::KEY_ID => Some(KEY_ID_LOCK.get(key.nspace)), + _ => None, + }; + + let tx = self + .conn + .unchecked_transaction() + .context(err!("Failed to initialize transaction."))?; + + // Load the key_id and complete the access control tuple. + let access = Self::load_access_tuple(&tx, key, key_type, caller_uid).context(err!())?; + + // KEY ID LOCK 2/2 + // If we did not get a key id lock by now, it was because we got a key descriptor + // without a key id. At this point we got the key id, so we can try and get a lock. + // However, we cannot block here, because we are in the middle of the transaction. + // So first we try to get the lock non blocking. If that fails, we roll back the + // transaction and block until we get the lock. After we successfully got the lock, + // we start a new transaction and load the access tuple again. + // + // We don't need to perform access control again, because we already established + // that the caller had access to the given key. But we need to make sure that the + // key id still exists. So we have to load the key entry by key id this time. + let (key_id_guard, tx) = match key_id_guard { + None => match KEY_ID_LOCK.try_get(access.key_id) { + None => { + // Roll back the transaction. + tx.rollback() + .context(err!("Failed to roll back transaction."))?; + + // Block until we have a key id lock. + let key_id_guard = KEY_ID_LOCK.get(access.key_id); + + // Create a new transaction. + let tx = self + .conn + .unchecked_transaction() + .context(err!("Failed to initialize transaction."))?; + + Self::load_access_tuple( + &tx, + // This time we have to load the key by the retrieved key id, because the + // alias may have been rebound after we rolled back the transaction. + &KeyDescriptor { + domain: Domain::KEY_ID, + nspace: access.key_id, + ..Default::default() + }, + key_type, + caller_uid, + ) + .context(err!("(deferred key lock)"))?; + (key_id_guard, tx) + } + Some(l) => (l, tx), + }, + Some(key_id_guard) => (key_id_guard, tx), + }; + + let key_entry = + Self::load_key_components(&tx, load_bits, key_id_guard.id()).context(err!())?; + + tx.commit().context(err!("Failed to commit transaction."))?; + + Ok((key_id_guard, key_entry)) + } + + fn create_key_entry_internal( + tx: &Transaction, + domain: &Domain, + namespace: &i64, + key_type: KeyType, + km_uuid: &Uuid, + ) -> Result { + match *domain { + Domain::APP | Domain::SELINUX => {} + _ => { + return Err(KsError::sys()) + .context(err!("Domain {:?} must be either App or SELinux.", domain)); + } + } + Ok(KEY_ID_LOCK.get( + Self::insert_with_retry(|id| { + tx.execute( + "INSERT into persistent.keyentry + (id, key_type, domain, namespace, alias, state, km_uuid) + VALUES(?, ?, ?, ?, NULL, ?, ?);", + params![ + id, + key_type, + domain.0 as u32, + *namespace, + KeyLifeCycle::Existing, + km_uuid, + ], + ) + }) + .context(err!())?, + )) + } + + // Generates a random id and passes it to the given function, which will + // try to insert it into a database. If that insertion fails, retry; + // otherwise return the id. + fn insert_with_retry(inserter: impl Fn(i64) -> rusqlite::Result) -> Result { + loop { + let newid: i64 = match random() { + Self::UNASSIGNED_KEY_ID => continue, // UNASSIGNED_KEY_ID cannot be assigned. + i => i, + }; + match inserter(newid) { + // If the id already existed, try again. + Err(rusqlite::Error::SqliteFailure( + libsqlite3_sys::Error { + code: libsqlite3_sys::ErrorCode::ConstraintViolation, + extended_code: libsqlite3_sys::SQLITE_CONSTRAINT_UNIQUE, + }, + _, + )) => (), + Err(e) => { + return Err(e).context(err!("failed to insert into database.")); + } + _ => return Ok(newid), + } + } + } + + fn insert_keyparameter_internal( + tx: &Transaction, + key_id: &KeyIdGuard, + params: &[KeyParameter], + ) -> Result<()> { + let mut stmt = tx + .prepare( + "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level) + VALUES (?, ?, ?, ?);", + ) + .context(err!("Failed to prepare statement."))?; + + for p in params.iter() { + stmt.insert(params![ + key_id.0, + p.get_tag().0, + p.key_parameter_value(), + p.security_level().0 + ]) + .with_context(|| err!("Failed to insert {:?}", p))?; + } + Ok(()) + } + + /// Store a new key in a single transaction. + /// The function creates a new key entry, populates the blob, key parameter, and metadata + /// fields, and rebinds the given alias to the new key. + /// The boolean returned is a hint for the garbage collector. If true, a key was replaced, + /// is now unreferenced and needs to be collected. + pub fn store_new_key( + &mut self, + key: &KeyDescriptor, + key_type: KeyType, + params: &[KeyParameter], + blob_info: &BlobInfo, + cert_info: &CertificateInfo, + metadata: &KeyMetaData, + km_uuid: &Uuid, + ) -> Result { + let _wp = wd::watch("KeystoreDB::store_new_key"); + + let (alias, domain, namespace) = match key { + KeyDescriptor { + alias: Some(alias), + domain: Domain::APP, + nspace, + blob: None, + } + | KeyDescriptor { + alias: Some(alias), + domain: Domain::SELINUX, + nspace, + blob: None, + } => (alias, key.domain, nspace), + _ => { + return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(err!("Need alias and domain must be APP or SELINUX.")); + } + }; + self.with_transaction(Immediate("TX_store_new_key"), |tx| { + let key_id = Self::create_key_entry_internal(tx, &domain, namespace, key_type, km_uuid) + .context("Trying to create new key entry.")?; + let BlobInfo { + blob, + metadata: blob_metadata, + superseded_blob, + } = *blob_info; + + // In some occasions the key blob is already upgraded during the import. + // In order to make sure it gets properly deleted it is inserted into the + // database here and then immediately replaced by the superseding blob. + // The garbage collector will then subject the blob to deleteKey of the + // KM back end to permanently invalidate the key. + let need_gc = if let Some((blob, blob_metadata)) = superseded_blob { + Self::set_blob_internal( + tx, + key_id.id(), + SubComponentType::KEY_BLOB, + Some(blob), + Some(blob_metadata), + ) + .context("Trying to insert superseded key blob.")?; + true + } else { + false + }; + + Self::set_blob_internal( + tx, + key_id.id(), + SubComponentType::KEY_BLOB, + Some(blob), + Some(blob_metadata), + ) + .context("Trying to insert the key blob.")?; + if let Some(cert) = &cert_info.cert { + Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT, Some(cert), None) + .context("Trying to insert the certificate.")?; + } + if let Some(cert_chain) = &cert_info.cert_chain { + Self::set_blob_internal( + tx, + key_id.id(), + SubComponentType::CERT_CHAIN, + Some(cert_chain), + None, + ) + .context("Trying to insert the certificate chain.")?; + } + Self::insert_keyparameter_internal(tx, &key_id, params) + .context("Trying to insert key parameters.")?; + metadata + .store_in_db(key_id.id(), tx) + .context("Trying to insert key metadata.")?; + + Ok(key_id) + }) + .context(err!()) + } + + /// Loads super key of a given user, if exists + pub fn load_super_key( + &mut self, + key_type: &SuperKeyType, + user_id: u32, + ) -> Result> { + let _wp = wd::watch("KeystoreDB::load_super_key"); + + self.with_transaction(Immediate("TX_load_super_key"), |tx| { + let key_descriptor = KeyDescriptor { + domain: Domain::APP, + nspace: user_id as i64, + alias: Some(key_type.alias.into()), + blob: None, + }; + let id = Self::load_key_entry_id(tx, &key_descriptor, KeyType::Super); + match id { + Ok(id) => { + let key_entry = Self::load_key_components(tx, KeyEntryLoadBits::KM, id) + .context(err!("Failed to load key entry."))?; + Ok(Some((KEY_ID_LOCK.get(id), key_entry))) + } + Err(error) => match error.root_cause().downcast_ref::() { + Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None), + _ => Err(error).context(err!()), + }, + } + }) + .context(err!()) + } + + /// Stores a super key in the database. + pub fn store_super_key( + &mut self, + user_id: u32, + key_type: &SuperKeyType, + blob: &[u8], + blob_metadata: &BlobMetaData, + key_metadata: &KeyMetaData, + ) -> Result { + let _wp = wd::watch("KeystoreDB::store_super_key"); + + self.with_transaction(Immediate("TX_store_super_key"), |tx| { + let key_id = Self::insert_with_retry(|id| { + tx.execute( + "INSERT into persistent.keyentry + (id, key_type, domain, namespace, alias, state, km_uuid) + VALUES(?, ?, ?, ?, ?, ?, ?);", + params![ + id, + KeyType::Super, + Domain::APP.0, + user_id as i64, + key_type.alias, + KeyLifeCycle::Live, + &KEYSTORE_UUID, + ], + ) + }) + .context("Failed to insert into keyentry table.")?; + + key_metadata + .store_in_db(key_id, tx) + .context("KeyMetaData::store_in_db failed")?; + + Self::set_blob_internal( + tx, + key_id, + SubComponentType::KEY_BLOB, + Some(blob), + Some(blob_metadata), + ) + .context("Failed to store key blob.")?; + + Self::load_key_components(tx, KeyEntryLoadBits::KM, key_id) + .context("Trying to load key components.") + }) + .context(err!()) + } + + fn set_blob_internal( + tx: &Transaction, + key_id: i64, + sc_type: SubComponentType, + blob: Option<&[u8]>, + blob_metadata: Option<&BlobMetaData>, + ) -> Result<()> { + match (blob, sc_type) { + (Some(blob), _) => { + // Mark any previous blobentry(s) of the same type for the same key as superseded. + tx.execute( + "UPDATE persistent.blobentry SET state = ? + WHERE keyentryid = ? AND subcomponent_type = ?", + params![BlobState::Superseded, key_id, sc_type], + ) + .context(err!( + "Failed to mark prior {sc_type:?} blobentrys for {key_id} as superseded" + ))?; + + // Now insert the new, un-superseded, blob. (If this fails, the marking of + // old blobs as superseded will be rolled back, because we're inside a + // transaction.) + tx.execute( + "INSERT INTO persistent.blobentry + (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);", + params![sc_type, key_id, blob], + ) + .context(err!("Failed to insert blob."))?; + + if let Some(blob_metadata) = blob_metadata { + let blob_id = tx + .query_row("SELECT MAX(id) FROM persistent.blobentry;", [], |row| { + row.get(0) + }) + .context(err!("Failed to get new blob id."))?; + + blob_metadata + .store_in_db(blob_id, tx) + .context(err!("Trying to store blob metadata."))?; + } + } + (None, SubComponentType::CERT) | (None, SubComponentType::CERT_CHAIN) => { + tx.execute( + "DELETE FROM persistent.blobentry + WHERE subcomponent_type = ? AND keyentryid = ?;", + params![sc_type, key_id], + ) + .context(err!("Failed to delete blob."))?; + } + (None, _) => { + return Err(KsError::sys()) + .context(err!("Other blobs cannot be deleted in this way.")); + } + } + Ok(()) + } + + /// Set a new blob and associates it with the given key id. Each blob + /// has a sub component type. + /// Each key can have one of each sub component type associated. If more + /// are added only the most recent can be retrieved, and superseded blobs + /// will get garbage collected. + /// Components SubComponentType::CERT and SubComponentType::CERT_CHAIN can be + /// removed by setting blob to None. + pub fn set_blob( + &mut self, + key_id: &KeyIdGuard, + sc_type: SubComponentType, + blob: Option<&[u8]>, + blob_metadata: Option<&BlobMetaData>, + ) -> Result<()> { + let _wp = wd::watch("KeystoreDB::set_blob"); + + self.with_transaction(Immediate("TX_set_blob"), |tx| { + Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata) + }) + .context(err!()) + } + + fn delete_received_grants(tx: &Transaction, user_id: u32) -> Result { + let updated = tx + .execute( + &format!("DELETE FROM persistent.grant WHERE cast ( (grantee/{AID_USER_OFFSET}) as int) = ?;"), + params![user_id], + ) + .context(format!( + "Trying to delete grants received by user ID {:?} from other apps.", + user_id + ))?; + Ok(updated != 0) + } + + /// Deletes all keys for the given user, including both client keys and super keys. + pub fn unbind_keys_for_user(&mut self, user_id: u32) -> Result<()> { + let _wp = wd::watch("KeystoreDB::unbind_keys_for_user"); + + self.with_transaction(Immediate("TX_unbind_keys_for_user"), |tx| { + Self::delete_received_grants(tx, user_id).context(format!( + "In unbind_keys_for_user. Failed to delete received grants for user ID {:?}.", + user_id + ))?; + + let mut stmt = tx + .prepare(&format!( + "SELECT id from persistent.keyentry + WHERE ( + key_type = ? + AND domain = ? + AND cast ( (namespace/{aid_user_offset}) as int) = ? + AND state = ? + ) OR ( + key_type = ? + AND namespace = ? + AND state = ? + );", + aid_user_offset = AID_USER_OFFSET + )) + .context(concat!( + "In unbind_keys_for_user. ", + "Failed to prepare the query to find the keys created by apps." + ))?; + + let mut rows = stmt + .query(params![ + // WHERE client key: + KeyType::Client, + Domain::APP.0 as u32, + user_id, + KeyLifeCycle::Live, + // OR super key: + KeyType::Super, + user_id, + KeyLifeCycle::Live + ]) + .context(err!("Failed to query the keys created by apps."))?; + + let mut key_ids: Vec = Vec::new(); + utils::with_rows_extract_all(&mut rows, |row| { + key_ids.push( + row.get(0) + .context("Failed to read key id of a key created by an app.")?, + ); + Ok(()) + }) + .context(err!())?; + + Ok(()) + }) + .context(err!()) + } + + /// Find the newest auth token matching the given predicate. + pub fn find_auth_token_entry(&self, p: F) -> Option + where + F: Fn(&AuthTokenEntry) -> bool, + { + self.perboot.find_auth_token_entry(p) + } + + /// Insert or replace the auth token based on (user_id, auth_id, auth_type) + pub fn insert_auth_token(&mut self, auth_token: &HardwareAuthToken) { + self.perboot + .insert_auth_token_entry(AuthTokenEntry::new(auth_token.clone(), BootTime::now())) + } + + /// Decrements the usage count of a limited use key. This function first checks whether the + /// usage has been exhausted, if not, decreases the usage count. If the usage count reaches + /// zero, the key also gets marked unreferenced and scheduled for deletion. + /// Returns Ok(true) if the key was marked unreferenced as a hint to the garbage collector. + pub fn check_and_update_key_usage_count(&mut self, key_id: i64) -> Result<()> { + let _wp = wd::watch("KeystoreDB::check_and_update_key_usage_count"); + + self.with_transaction(Immediate("TX_check_and_update_key_usage_count"), |tx| { + let limit: Option = tx + .query_row( + "SELECT data FROM persistent.keyparameter WHERE keyentryid = ? AND tag = ?;", + params![key_id, Tag::USAGE_COUNT_LIMIT.0], + |row| row.get(0), + ) + .optional() + .context("Trying to load usage count")?; + + let limit = limit + .ok_or(KsError::Km(ErrorCode::INVALID_KEY_BLOB)) + .context("The Key no longer exists. Key is exhausted.")?; + + tx.execute( + "UPDATE persistent.keyparameter + SET data = data - 1 + WHERE keyentryid = ? AND tag = ? AND data > 0;", + params![key_id, Tag::USAGE_COUNT_LIMIT.0], + ) + .context("Failed to update key usage count.")?; + + match limit { + 1 => { + Self::mark_unreferenced(tx, key_id) + .context("Trying to mark limited use key for deletion.")?; + Ok(()) + } + 0 => Err(KsError::Km(ErrorCode::INVALID_KEY_BLOB)).context("Key is exhausted."), + _ => Ok(()), + } + }) + .context(err!()) + } } /// Database representation of the monotonic time retrieved from the system call clock_gettime with @@ -243,7 +1316,7 @@ impl BootTime { } impl ToSql for BootTime { - fn to_sql(&self) -> rusqlite::Result { + fn to_sql(&self) -> rusqlite::Result> { Ok(ToSqlOutput::Owned(Value::Integer(self.0))) } } @@ -265,7 +1338,10 @@ pub struct AuthTokenEntry { impl AuthTokenEntry { fn new(auth_token: HardwareAuthToken, time_received: BootTime) -> Self { - AuthTokenEntry { auth_token, time_received } + AuthTokenEntry { + auth_token, + time_received, + } } /// Checks if this auth token satisfies the given authentication information. @@ -296,3 +1372,674 @@ impl AuthTokenEntry { self.auth_token.challenge } } + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum KeyType { + /// This is a client key type. These keys are created or imported through the Keystore 2.0 + /// AIDL interface android.system.keystore2. + Client, + /// This is a super key type. These keys are created by keystore itself and used to encrypt + /// other key blobs to provide LSKF binding. + Super, +} + +impl ToSql for KeyType { + fn to_sql(&self) -> rusqlite::Result> { + Ok(ToSqlOutput::Owned(Value::Integer(match self { + KeyType::Client => 0, + KeyType::Super => 1, + }))) + } +} + +impl FromSql for KeyType { + fn column_result(value: ValueRef) -> FromSqlResult { + match i64::column_result(value)? { + 0 => Ok(KeyType::Client), + 1 => Ok(KeyType::Super), + v => Err(FromSqlError::OutOfRange(v)), + } + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +enum KeyLifeCycle { + /// Existing keys have a key ID but are not fully populated yet. + /// This is a transient state. If Keystore finds any such keys when it starts up, it must move + /// them to Unreferenced for garbage collection. + Existing, + /// A live key is fully populated and usable by clients. + Live, + /// An unreferenced key is scheduled for garbage collection. + Unreferenced, +} + +impl ToSql for KeyLifeCycle { + fn to_sql(&self) -> rusqlite::Result> { + match self { + Self::Existing => Ok(ToSqlOutput::Owned(Value::Integer(0))), + Self::Live => Ok(ToSqlOutput::Owned(Value::Integer(1))), + Self::Unreferenced => Ok(ToSqlOutput::Owned(Value::Integer(2))), + } + } +} + +impl FromSql for KeyLifeCycle { + fn column_result(value: ValueRef) -> FromSqlResult { + match i64::column_result(value)? { + 0 => Ok(KeyLifeCycle::Existing), + 1 => Ok(KeyLifeCycle::Live), + 2 => Ok(KeyLifeCycle::Unreferenced), + v => Err(FromSqlError::OutOfRange(v)), + } + } +} + +impl_metadata!( + /// A set of metadata for key entries. + #[derive(Debug, Default, Eq, PartialEq)] + pub struct KeyMetaData; + /// A metadata entry for key entries. + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] + pub enum KeyMetaEntry { + /// Date of the creation of the key entry. + CreationDate(DateTime) with accessor creation_date, + /// Expiration date for attestation keys. + AttestationExpirationDate(DateTime) with accessor attestation_expiration_date, + /// CBOR Blob that represents a COSE_Key and associated metadata needed for remote + /// provisioning + AttestationMacedPublicKey(Vec) with accessor attestation_maced_public_key, + /// Vector representing the raw public key so results from the server can be matched + /// to the right entry + AttestationRawPubKey(Vec) with accessor attestation_raw_pub_key, + /// SEC1 public key for ECDH encryption + Sec1PublicKey(Vec) with accessor sec1_public_key, + // --- ADD NEW META DATA FIELDS HERE --- + // For backwards compatibility add new entries only to + // end of this list and above this comment. + }; +); + +impl KeyMetaData { + fn load_from_db(key_id: i64, tx: &Transaction) -> Result { + let mut stmt = tx + .prepare( + "SELECT tag, data from persistent.keymetadata + WHERE keyentryid = ?;", + ) + .context(err!("KeyMetaData::load_from_db: prepare statement failed."))?; + + let mut metadata: HashMap = Default::default(); + + let mut rows = stmt + .query(params![key_id]) + .context(err!("KeyMetaData::load_from_db: query failed."))?; + utils::with_rows_extract_all(&mut rows, |row| { + let db_tag: i64 = row.get(0).context("Failed to read tag.")?; + metadata.insert( + db_tag, + KeyMetaEntry::new_from_sql(db_tag, &SqlField::new(1, row)) + .context("Failed to read KeyMetaEntry.")?, + ); + Ok(()) + }) + .context(err!("KeyMetaData::load_from_db."))?; + + Ok(Self { data: metadata }) + } + + fn store_in_db(&self, key_id: i64, tx: &Transaction) -> Result<()> { + let mut stmt = tx + .prepare( + "INSERT or REPLACE INTO persistent.keymetadata (keyentryid, tag, data) + VALUES (?, ?, ?);", + ) + .context(err!( + "KeyMetaData::store_in_db: Failed to prepare statement." + ))?; + + let iter = self.data.iter(); + for (tag, entry) in iter { + stmt.insert(params![key_id, tag, entry,]) + .with_context(|| err!("KeyMetaData::store_in_db: Failed to insert {:?}", entry))?; + } + Ok(()) + } +} + +impl_metadata!( + /// A set of metadata for key blobs. + #[derive(Debug, Default, Eq, PartialEq)] + pub struct BlobMetaData; + /// A metadata entry for key blobs. + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] + pub enum BlobMetaEntry { + /// If present, indicates that the blob is encrypted with another key or a key derived + /// from a password. + EncryptedBy(EncryptedBy) with accessor encrypted_by, + /// If the blob is password encrypted this field is set to the + /// salt used for the key derivation. + Salt(Vec) with accessor salt, + /// If the blob is encrypted, this field is set to the initialization vector. + Iv(Vec) with accessor iv, + /// If the blob is encrypted, this field holds the AEAD TAG. + AeadTag(Vec) with accessor aead_tag, + /// The uuid of the owning KeyMint instance. + KmUuid(Uuid) with accessor km_uuid, + /// If the key is ECDH encrypted, this is the ephemeral public key + PublicKey(Vec) with accessor public_key, + /// If the key is encrypted with a MaxBootLevel key, this is the boot level + /// of that key + MaxBootLevel(i32) with accessor max_boot_level, + // --- ADD NEW META DATA FIELDS HERE --- + // For backwards compatibility add new entries only to + // end of this list and above this comment. + }; +); + +impl BlobMetaData { + fn load_from_db(blob_id: i64, tx: &Transaction) -> Result { + let mut stmt = tx + .prepare( + "SELECT tag, data from persistent.blobmetadata + WHERE blobentryid = ?;", + ) + .context(err!( + "BlobMetaData::load_from_db: prepare statement failed." + ))?; + + let mut metadata: HashMap = Default::default(); + + let mut rows = stmt + .query(params![blob_id]) + .context(err!("query failed."))?; + utils::with_rows_extract_all(&mut rows, |row| { + let db_tag: i64 = row.get(0).context("Failed to read tag.")?; + metadata.insert( + db_tag, + BlobMetaEntry::new_from_sql(db_tag, &SqlField::new(1, row)) + .context("Failed to read BlobMetaEntry.")?, + ); + Ok(()) + }) + .context(err!("BlobMetaData::load_from_db"))?; + + Ok(Self { data: metadata }) + } + + fn store_in_db(&self, blob_id: i64, tx: &Transaction) -> Result<()> { + let mut stmt = tx + .prepare( + "INSERT or REPLACE INTO persistent.blobmetadata (blobentryid, tag, data) + VALUES (?, ?, ?);", + ) + .context(err!( + "BlobMetaData::store_in_db: Failed to prepare statement.", + ))?; + + let iter = self.data.iter(); + for (tag, entry) in iter { + stmt.insert(params![blob_id, tag, entry,]) + .with_context(|| err!("BlobMetaData::store_in_db: Failed to insert {:?}", entry))?; + } + Ok(()) + } +} + +/// Uuid representation that can be stored in the database. +/// Right now it can only be initialized from SecurityLevel. +/// Once KeyMint provides a UUID type a corresponding From impl shall be added. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Uuid([u8; 16]); + +impl Deref for Uuid { + type Target = [u8; 16]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ToSql for Uuid { + fn to_sql(&self) -> rusqlite::Result { + self.0.to_sql() + } +} + +impl FromSql for Uuid { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let blob = Vec::::column_result(value)?; + if blob.len() != 16 { + return Err(FromSqlError::OutOfRange(blob.len() as i64)); + } + let mut arr = [0u8; 16]; + arr.copy_from_slice(&blob); + Ok(Self(arr)) + } +} + +/// Key entries that are not associated with any KeyMint instance, such as pure certificate +/// entries are associated with this UUID. +pub static KEYSTORE_UUID: Uuid = Uuid([ + 0x41, 0xe3, 0xb9, 0xce, 0x27, 0x58, 0x4e, 0x91, 0xbc, 0xfd, 0xa5, 0x5d, 0x91, 0x85, 0xab, 0x11, +]); + +/// Indicates how the sensitive part of this key blob is encrypted. +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum EncryptedBy { + /// The keyblob is encrypted by a user password. + /// In the database this variant is represented as NULL. + Password, + /// The keyblob is encrypted by another key with wrapped key id. + /// In the database this variant is represented as non NULL value + /// that is convertible to i64, typically NUMERIC. + KeyId(i64), +} + +impl ToSql for EncryptedBy { + fn to_sql(&self) -> rusqlite::Result { + match self { + Self::Password => Ok(ToSqlOutput::Owned(Value::Null)), + Self::KeyId(id) => id.to_sql(), + } + } +} + +impl FromSql for EncryptedBy { + fn column_result(value: ValueRef) -> FromSqlResult { + match value { + ValueRef::Null => Ok(Self::Password), + _ => Ok(Self::KeyId(i64::column_result(value)?)), + } + } +} + +/// A database representation of wall clock time. DateTime stores unix epoch time as +/// i64 in milliseconds. +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct DateTime(i64); + +/// Error type returned when creating DateTime or converting it from and to +/// SystemTime. +#[derive(thiserror::Error, Debug)] +pub enum DateTimeError { + /// This is returned when SystemTime and Duration computations fail. + #[error(transparent)] + SystemTimeError(#[from] SystemTimeError), + + /// This is returned when type conversions fail. + #[error(transparent)] + TypeConversion(#[from] std::num::TryFromIntError), + + /// This is returned when checked time arithmetic failed. + #[error("Time arithmetic failed.")] + TimeArithmetic, +} + +impl DateTime { + /// Constructs a new DateTime object denoting the current time. This may fail during + /// conversion to unix epoch time and during conversion to the internal i64 representation. + pub fn now() -> Result { + Ok(Self( + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_millis() + .try_into()?, + )) + } + + /// Constructs a new DateTime object from milliseconds. + pub fn from_millis_epoch(millis: i64) -> Self { + Self(millis) + } + + /// Returns unix epoch time in milliseconds. + pub fn to_millis_epoch(self) -> i64 { + self.0 + } +} + +impl ToSql for DateTime { + fn to_sql(&self) -> rusqlite::Result { + Ok(ToSqlOutput::Owned(Value::Integer(self.0))) + } +} + +impl FromSql for DateTime { + fn column_result(value: ValueRef) -> FromSqlResult { + Ok(Self(i64::column_result(value)?)) + } +} + +impl TryInto for DateTime { + type Error = DateTimeError; + + fn try_into(self) -> Result { + // We want to construct a SystemTime representation equivalent to self, denoting + // a point in time THEN, but we cannot set the time directly. We can only construct + // a SystemTime denoting NOW, and we can get the duration between EPOCH and NOW, + // and between EPOCH and THEN. With this common reference we can construct the + // duration between NOW and THEN which we can add to our SystemTime representation + // of NOW to get a SystemTime representation of THEN. + // Durations can only be positive, thus the if statement below. + let now = SystemTime::now(); + let now_epoch = now.duration_since(SystemTime::UNIX_EPOCH)?; + let then_epoch = Duration::from_millis(self.0.try_into()?); + Ok(if now_epoch > then_epoch { + // then = now - (now_epoch - then_epoch) + now_epoch + .checked_sub(then_epoch) + .and_then(|d| now.checked_sub(d)) + .ok_or(DateTimeError::TimeArithmetic)? + } else { + // then = now + (then_epoch - now_epoch) + then_epoch + .checked_sub(now_epoch) + .and_then(|d| now.checked_add(d)) + .ok_or(DateTimeError::TimeArithmetic)? + }) + } +} + +impl TryFrom for DateTime { + type Error = DateTimeError; + + fn try_from(t: SystemTime) -> Result { + Ok(Self( + t.duration_since(SystemTime::UNIX_EPOCH)? + .as_millis() + .try_into()?, + )) + } +} + +/// Current state of a `blobentry` row. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Default)] +enum BlobState { + #[default] + /// Current blobentry (of its `subcomponent_type`) for the associated key. + Current, + /// Blobentry that is no longer the current blob (of its `subcomponent_type`) for the associated + /// key. + Superseded, + /// Blobentry for a key that no longer exists. + Orphaned, +} + +impl ToSql for BlobState { + fn to_sql(&self) -> rusqlite::Result { + match self { + Self::Current => Ok(ToSqlOutput::Owned(Value::Integer(0))), + Self::Superseded => Ok(ToSqlOutput::Owned(Value::Integer(1))), + Self::Orphaned => Ok(ToSqlOutput::Owned(Value::Integer(2))), + } + } +} + +impl FromSql for BlobState { + fn column_result(value: ValueRef) -> FromSqlResult { + match i64::column_result(value)? { + 0 => Ok(Self::Current), + 1 => Ok(Self::Superseded), + 2 => Ok(Self::Orphaned), + v => Err(FromSqlError::OutOfRange(v)), + } + } +} + +/// Keys have a KeyMint blob component and optional public certificate and +/// certificate chain components. +/// KeyEntryLoadBits is a bitmap that indicates to `KeystoreDB::load_key_entry` +/// which components shall be loaded from the database if present. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +pub struct KeyEntryLoadBits(u32); + +impl KeyEntryLoadBits { + /// Indicate to `KeystoreDB::load_key_entry` that no component shall be loaded. + pub const NONE: KeyEntryLoadBits = Self(0); + /// Indicate to `KeystoreDB::load_key_entry` that the KeyMint component shall be loaded. + pub const KM: KeyEntryLoadBits = Self(1); + /// Indicate to `KeystoreDB::load_key_entry` that the Public components shall be loaded. + pub const PUBLIC: KeyEntryLoadBits = Self(2); + /// Indicate to `KeystoreDB::load_key_entry` that both components shall be loaded. + pub const BOTH: KeyEntryLoadBits = Self(3); + + /// Returns true if this object indicates that the public components shall be loaded. + pub const fn load_public(&self) -> bool { + self.0 & Self::PUBLIC.0 != 0 + } + + /// Returns true if the object indicates that the KeyMint component shall be loaded. + pub const fn load_km(&self) -> bool { + self.0 & Self::KM.0 != 0 + } +} + +static KEY_ID_LOCK: LazyLock = LazyLock::new(KeyIdLockDb::new); + +struct KeyIdLockDb { + locked_keys: Mutex>, + cond_var: Condvar, +} + +/// A locked key. While a guard exists for a given key id, the same key cannot be loaded +/// from the database a second time. Most functions manipulating the key blob database +/// require a KeyIdGuard. +#[derive(Debug)] +pub struct KeyIdGuard(i64); + +impl KeyIdLockDb { + fn new() -> Self { + Self { + locked_keys: Mutex::new(HashSet::new()), + cond_var: Condvar::new(), + } + } + + /// This function blocks until an exclusive lock for the given key entry id can + /// be acquired. It returns a guard object, that represents the lifecycle of the + /// acquired lock. + fn get(&self, key_id: i64) -> KeyIdGuard { + let mut locked_keys = self.locked_keys.lock().unwrap(); + while locked_keys.contains(&key_id) { + locked_keys = self.cond_var.wait(locked_keys).unwrap(); + } + locked_keys.insert(key_id); + KeyIdGuard(key_id) + } + + /// This function attempts to acquire an exclusive lock on a given key id. If the + /// given key id is already taken the function returns None immediately. If a lock + /// can be acquired this function returns a guard object, that represents the + /// lifecycle of the acquired lock. + fn try_get(&self, key_id: i64) -> Option { + let mut locked_keys = self.locked_keys.lock().unwrap(); + if locked_keys.insert(key_id) { + Some(KeyIdGuard(key_id)) + } else { + None + } + } +} + +impl KeyIdGuard { + /// Get the numeric key id of the locked key. + pub fn id(&self) -> i64 { + self.0 + } +} + +impl Drop for KeyIdGuard { + fn drop(&mut self) { + let mut locked_keys = KEY_ID_LOCK.locked_keys.lock().unwrap(); + locked_keys.remove(&self.0); + drop(locked_keys); + KEY_ID_LOCK.cond_var.notify_all(); + } +} + +/// This type represents a certificate and certificate chain entry for a key. +#[derive(Debug, Default)] +pub struct CertificateInfo { + cert: Option>, + cert_chain: Option>, +} + +/// This type represents a Blob with its metadata and an optional superseded blob. +#[derive(Debug)] +pub struct BlobInfo<'a> { + blob: &'a [u8], + metadata: &'a BlobMetaData, + /// Superseded blobs are an artifact of legacy import. In some rare occasions + /// the key blob needs to be upgraded during import. In that case two + /// blob are imported, the superseded one will have to be imported first, + /// so that the garbage collector can reap it. + superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>, +} + +impl<'a> BlobInfo<'a> { + /// Create a new instance of blob info with blob and corresponding metadata + /// and no superseded blob info. + pub fn new(blob: &'a [u8], metadata: &'a BlobMetaData) -> Self { + Self { + blob, + metadata, + superseded_blob: None, + } + } + + /// Create a new instance of blob info with blob and corresponding metadata + /// as well as superseded blob info. + pub fn new_with_superseded( + blob: &'a [u8], + metadata: &'a BlobMetaData, + superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>, + ) -> Self { + Self { + blob, + metadata, + superseded_blob, + } + } +} + +impl CertificateInfo { + /// Constructs a new CertificateInfo object from `cert` and `cert_chain` + pub fn new(cert: Option>, cert_chain: Option>) -> Self { + Self { cert, cert_chain } + } + + /// Take the cert + pub fn take_cert(&mut self) -> Option> { + self.cert.take() + } + + /// Take the cert chain + pub fn take_cert_chain(&mut self) -> Option> { + self.cert_chain.take() + } +} + +/// This type represents a certificate chain with a private key corresponding to the leaf +/// certificate. TODO(jbires): This will be used in a follow-on CL, for now it's used in the tests. +pub struct CertificateChain { + /// A KM key blob + pub private_key: Vec, + /// A batch cert for private_key + pub batch_cert: Vec, + /// A full certificate chain from root signing authority to private_key, including batch_cert + /// for convenience. + pub cert_chain: Vec, +} + +/// This type represents a Keystore 2.0 key entry. +/// An entry has a unique `id` by which it can be found in the database. +/// It has a security level field, key parameters, and three optional fields +/// for the KeyMint blob, public certificate and a public certificate chain. +#[derive(Debug, Default, Eq, PartialEq)] +pub struct KeyEntry { + id: i64, + key_blob_info: Option<(Vec, BlobMetaData)>, + cert: Option>, + cert_chain: Option>, + km_uuid: Uuid, + parameters: Vec, + metadata: KeyMetaData, + pure_cert: bool, +} + +impl KeyEntry { + /// Returns the unique id of the Key entry. + pub fn id(&self) -> i64 { + self.id + } + /// Exposes the optional KeyMint blob. + pub fn key_blob_info(&self) -> &Option<(Vec, BlobMetaData)> { + &self.key_blob_info + } + /// Extracts the Optional KeyMint blob including its metadata. + pub fn take_key_blob_info(&mut self) -> Option<(Vec, BlobMetaData)> { + self.key_blob_info.take() + } + /// Exposes the optional public certificate. + pub fn cert(&self) -> &Option> { + &self.cert + } + /// Extracts the optional public certificate. + pub fn take_cert(&mut self) -> Option> { + self.cert.take() + } + /// Extracts the optional public certificate_chain. + pub fn take_cert_chain(&mut self) -> Option> { + self.cert_chain.take() + } + /// Returns the uuid of the owning KeyMint instance. + pub fn km_uuid(&self) -> &Uuid { + &self.km_uuid + } + /// Consumes this key entry and extracts the keyparameters from it. + pub fn into_key_parameters(self) -> Vec { + self.parameters + } + /// Exposes the key metadata of this key entry. + pub fn metadata(&self) -> &KeyMetaData { + &self.metadata + } + /// This returns true if the entry is a pure certificate entry with no + /// private key component. + pub fn pure_cert(&self) -> bool { + self.pure_cert + } +} + +/// Indicates the sub component of a key entry for persistent storage. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +pub struct SubComponentType(u32); +impl SubComponentType { + /// Persistent identifier for a key blob. + pub const KEY_BLOB: SubComponentType = Self(0); + /// Persistent identifier for a certificate blob. + pub const CERT: SubComponentType = Self(1); + /// Persistent identifier for a certificate chain blob. + pub const CERT_CHAIN: SubComponentType = Self(2); +} + +impl ToSql for SubComponentType { + fn to_sql(&self) -> rusqlite::Result { + self.0.to_sql() + } +} + +impl FromSql for SubComponentType { + fn column_result(value: ValueRef) -> FromSqlResult { + Ok(Self(u32::column_result(value)?)) + } +} + +/// Access information for a key. +#[derive(Debug)] +struct KeyAccessInfo { + key_id: i64, + descriptor: KeyDescriptor, + vector: Option, +} diff --git a/libs/rust/src/keymaster/enforcements.rs b/libs/rust/src/keymaster/enforcements.rs new file mode 100644 index 0000000..a36c233 --- /dev/null +++ b/libs/rust/src/keymaster/enforcements.rs @@ -0,0 +1,832 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This is the Keystore 2.0 Enforcements module. +use crate::android::hardware::security::keymint::ErrorCode; +use crate::android::system::keystore2::ResponseCode::ResponseCode; +// TODO: more description to follow. +use crate::err; + +use crate::android::hardware::security::keymint::{ + Algorithm::Algorithm, ErrorCode::ErrorCode as Ec, HardwareAuthToken::HardwareAuthToken, + HardwareAuthenticatorType::HardwareAuthenticatorType, + KeyParameter::KeyParameter as KmKeyParameter, KeyPurpose::KeyPurpose, Tag::Tag, +}; +use crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken; +use crate::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING, + OperationChallenge::OperationChallenge, +}; +use crate::global::{get_timestamp_service, DB, ENFORCEMENTS, SUPER_KEY}; +use crate::keymaster::db::{AuthTokenEntry, BootTime}; +use crate::keymaster::error::{map_binder_status, KsError as Error}; +use crate::keymaster::key_parameter::{KeyParameter, KeyParameterValue}; +use crate::keymaster::super_key::SuperEncryptionType; +use anyhow::{Context, Result}; +use std::{ + collections::{HashMap, HashSet}, + sync::{ + mpsc::{channel, Receiver, Sender, TryRecvError}, + Arc, Mutex, Weak, + }, + time::SystemTime, +}; + +#[derive(Debug)] +enum AuthRequestState { + /// An outstanding per operation authorization request. + OpAuth, + /// An outstanding request for a timestamp token. + TimeStamp(Mutex>>), +} + +#[derive(Debug)] +struct AuthRequest { + state: AuthRequestState, + /// This need to be set to Some to fulfill an AuthRequestState::OpAuth. + hat: Mutex>, +} + +impl AuthRequest { + fn op_auth() -> Arc { + Arc::new(Self { + state: AuthRequestState::OpAuth, + hat: Mutex::new(None), + }) + } + + fn timestamp( + hat: HardwareAuthToken, + receiver: Receiver>, + ) -> Arc { + Arc::new(Self { + state: AuthRequestState::TimeStamp(Mutex::new(receiver)), + hat: Mutex::new(Some(hat)), + }) + } + + fn add_auth_token(&self, hat: HardwareAuthToken) { + *self.hat.lock().unwrap() = Some(hat) + } + + fn get_auth_tokens(&self) -> Result<(HardwareAuthToken, Option)> { + let hat = self + .hat + .lock() + .unwrap() + .take() + .ok_or(Error::Km(ErrorCode::ErrorCode::KEY_USER_NOT_AUTHENTICATED)) + .context(err!("No operation auth token received."))?; + + let tst = match &self.state { + AuthRequestState::TimeStamp(recv) => { + let result = recv + .lock() + .unwrap() + .recv() + .context("In get_auth_tokens: Sender disconnected.")?; + Some(result.context(err!( + "Worker responded with error \ + from generating timestamp token.", + ))?) + } + AuthRequestState::OpAuth => None, + }; + Ok((hat, tst)) + } +} + +/// DeferredAuthState describes how auth tokens and timestamp tokens need to be provided when +/// updating and finishing an operation. +#[derive(Debug)] +enum DeferredAuthState { + /// Used when an operation does not require further authorization. + NoAuthRequired, + /// Indicates that the operation requires an operation specific token. This means we have + /// to return an operation challenge to the client which should reward us with an + /// operation specific auth token. If it is not provided before the client calls update + /// or finish, the operation fails as not authorized. + OpAuthRequired, + /// Indicates that the operation requires a time stamp token. The auth token was already + /// loaded from the database, but it has to be accompanied by a time stamp token to inform + /// the target KM with a different clock about the time on the authenticators. + TimeStampRequired(HardwareAuthToken), + /// In this state the auth info is waiting for the deferred authorizations to come in. + /// We block on timestamp tokens, because we can always make progress on these requests. + /// The per-op auth tokens might never come, which means we fail if the client calls + /// update or finish before we got a per-op auth token. + Waiting(Arc), + /// In this state we have gotten all of the required tokens, we just cache them to + /// be used when the operation progresses. + Token(HardwareAuthToken, Option), +} + +/// Auth info hold all of the authorization related information of an operation. It is stored +/// in and owned by the operation. It is constructed by authorize_create and stays with the +/// operation until it completes. +#[derive(Debug)] +pub struct AuthInfo { + state: DeferredAuthState, + /// An optional key id required to update the usage count if the key usage is limited. + key_usage_limited: Option, + confirmation_token_receiver: Option>>>>>, +} + +struct TokenReceiverMap { + /// The map maps an outstanding challenge to a TokenReceiver. If an incoming Hardware Auth + /// Token (HAT) has the map key in its challenge field, it gets passed to the TokenReceiver + /// and the entry is removed from the map. In the case where no HAT is received before the + /// corresponding operation gets dropped, the entry goes stale. So every time the cleanup + /// counter (second field in the tuple) turns 0, the map is cleaned from stale entries. + /// The cleanup counter is decremented every time a new receiver is added. + /// and reset to TokenReceiverMap::CLEANUP_PERIOD + 1 after each cleanup. + map_and_cleanup_counter: Mutex<(HashMap, u8)>, +} + +impl Default for TokenReceiverMap { + fn default() -> Self { + Self { + map_and_cleanup_counter: Mutex::new((HashMap::new(), Self::CLEANUP_PERIOD + 1)), + } + } +} + +impl TokenReceiverMap { + /// There is a chance that receivers may become stale because their operation is dropped + /// without ever being authorized. So occasionally we iterate through the map and throw + /// out obsolete entries. + /// This is the number of calls to add_receiver between cleanups. + const CLEANUP_PERIOD: u8 = 25; + + pub fn add_auth_token(&self, hat: HardwareAuthToken) { + let recv = { + // Limit the scope of the mutex guard, so that it is not held while the auth token is + // added. + let mut map = self.map_and_cleanup_counter.lock().unwrap(); + let (ref mut map, _) = *map; + map.remove_entry(&hat.challenge) + }; + + if let Some((_, recv)) = recv { + recv.add_auth_token(hat); + } + } + + pub fn add_receiver(&self, challenge: i64, recv: TokenReceiver) { + let mut map = self.map_and_cleanup_counter.lock().unwrap(); + let (ref mut map, ref mut cleanup_counter) = *map; + map.insert(challenge, recv); + + *cleanup_counter -= 1; + if *cleanup_counter == 0 { + map.retain(|_, v| !v.is_obsolete()); + map.shrink_to_fit(); + *cleanup_counter = Self::CLEANUP_PERIOD + 1; + } + } +} + +#[derive(Debug)] +struct TokenReceiver(Weak); + +impl TokenReceiver { + fn is_obsolete(&self) -> bool { + self.0.upgrade().is_none() + } + + fn add_auth_token(&self, hat: HardwareAuthToken) { + if let Some(state_arc) = self.0.upgrade() { + state_arc.add_auth_token(hat); + } + } +} + +fn get_timestamp_token(challenge: i64) -> Result { + let dev = get_timestamp_service().expect(concat!( + "Secure Clock service must be present ", + "if TimeStampTokens are required." + )); + + map_binder_status(dev.generateTimeStamp(challenge)) +} + +async fn timestamp_token_request(challenge: i64, sender: Sender>) { + if let Err(e) = sender.send(get_timestamp_token(challenge)) { + log::info!( + concat!( + "Receiver hung up ", + "before timestamp token could be delivered. {:?}" + ), + e + ); + } +} + +impl AuthInfo { + /// This function gets called after an operation was successfully created. + /// It makes all the preparations required, so that the operation has all the authentication + /// related artifacts to advance on update and finish. + pub fn finalize_create_authorization(&mut self, challenge: i64) -> Option { + match &self.state { + DeferredAuthState::OpAuthRequired => { + let auth_request = AuthRequest::op_auth(); + let token_receiver = TokenReceiver(Arc::downgrade(&auth_request)); + ENFORCEMENTS.register_op_auth_receiver(challenge, token_receiver); + + self.state = DeferredAuthState::Waiting(auth_request); + Some(OperationChallenge { challenge }) + } + DeferredAuthState::TimeStampRequired(hat) => { + let hat = (*hat).clone(); + let (sender, receiver) = channel::>(); + let auth_request = AuthRequest::timestamp(hat, receiver); + tokio::spawn(async move { + timestamp_token_request(challenge, sender).await; + }); + self.state = DeferredAuthState::Waiting(auth_request); + None + } + _ => None, + } + } + + /// This function is the authorization hook called before operation update. + /// It returns the auth tokens required by the operation to commence update. + pub fn before_update(&mut self) -> Result<(Option, Option)> { + self.get_auth_tokens() + } + + /// This function is the authorization hook called before operation finish. + /// It returns the auth tokens required by the operation to commence finish. + /// The third token is a confirmation token. + pub fn before_finish( + &mut self, + ) -> Result<( + Option, + Option, + Option>, + )> { + let mut confirmation_token: Option> = None; + if let Some(ref confirmation_token_receiver) = self.confirmation_token_receiver { + let locked_receiver = confirmation_token_receiver.lock().unwrap(); + if let Some(ref receiver) = *locked_receiver { + loop { + match receiver.try_recv() { + // As long as we get tokens we loop and discard all but the most + // recent one. + Ok(t) => confirmation_token = Some(t), + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => { + log::error!(concat!( + "We got disconnected from the APC service, ", + "this should never happen." + )); + break; + } + } + } + } + } + self.get_auth_tokens() + .map(|(hat, tst)| (hat, tst, confirmation_token)) + } + + /// This function is the authorization hook called after finish succeeded. + /// As of this writing it checks if the key was a limited use key. If so it updates the + /// use counter of the key in the database. When the use counter is depleted, the key gets + /// marked for deletion and the garbage collector is notified. + pub fn after_finish(&self) -> Result<()> { + if let Some(key_id) = self.key_usage_limited { + // On the last successful use, the key gets deleted. In this case we + // have to notify the garbage collector. + DB.with(|db| { + db.borrow_mut() + .check_and_update_key_usage_count(key_id) + .context("Trying to update key usage count.") + }) + .context(err!())?; + } + Ok(()) + } + + /// This function returns the auth tokens as needed by the ongoing operation or fails + /// with ErrorCode::KEY_USER_NOT_AUTHENTICATED. If this was called for the first time + /// after a deferred authorization was requested by finalize_create_authorization, this + /// function may block on the generation of a time stamp token. It then moves the + /// tokens into the DeferredAuthState::Token state for future use. + fn get_auth_tokens(&mut self) -> Result<(Option, Option)> { + let deferred_tokens = if let DeferredAuthState::Waiting(ref auth_request) = self.state { + Some( + auth_request + .get_auth_tokens() + .context("In AuthInfo::get_auth_tokens.")?, + ) + } else { + None + }; + + if let Some((hat, tst)) = deferred_tokens { + self.state = DeferredAuthState::Token(hat, tst); + } + + match &self.state { + DeferredAuthState::NoAuthRequired => Ok((None, None)), + DeferredAuthState::Token(hat, tst) => Ok((Some((*hat).clone()), (*tst).clone())), + DeferredAuthState::OpAuthRequired | DeferredAuthState::TimeStampRequired(_) => { + Err(Error::Km(ErrorCode::ErrorCode::KEY_USER_NOT_AUTHENTICATED)).context(err!( + "No operation auth token requested??? \ + This should not happen." + )) + } + // This should not be reachable, because it should have been handled above. + DeferredAuthState::Waiting(_) => { + Err(Error::sys()).context(err!("AuthInfo::get_auth_tokens: Cannot be reached.",)) + } + } + } +} + +/// Enforcements data structure +#[derive(Default)] +pub struct Enforcements { + /// This hash set contains the user ids for whom the device is currently unlocked. If a user id + /// is not in the set, it implies that the device is locked for the user. + device_unlocked_set: Mutex>, + /// This field maps outstanding auth challenges to their operations. When an auth token + /// with the right challenge is received it is passed to the map using + /// TokenReceiverMap::add_auth_token() which removes the entry from the map. If an entry goes + /// stale, because the operation gets dropped before an auth token is received, the map + /// is cleaned up in regular intervals. + op_auth_map: TokenReceiverMap, + /// The enforcement module will try to get a confirmation token from this channel whenever + /// an operation that requires confirmation finishes. + confirmation_token_receiver: Arc>>>>, +} + +impl Enforcements { + /// Install the confirmation token receiver. The enforcement module will try to get a + /// confirmation token from this channel whenever an operation that requires confirmation + /// finishes. + pub fn install_confirmation_token_receiver( + &self, + confirmation_token_receiver: Receiver>, + ) { + *self.confirmation_token_receiver.lock().unwrap() = Some(confirmation_token_receiver); + } + + /// Checks if a create call is authorized, given key parameters and operation parameters. + /// It returns an optional immediate auth token which can be presented to begin, and an + /// AuthInfo object which stays with the authorized operation and is used to obtain + /// auth tokens and timestamp tokens as required by the operation. + /// With regard to auth tokens, the following steps are taken: + /// + /// If no key parameters are given (typically when the client is self managed + /// (see Domain.Blob)) nothing is enforced. + /// If the key is time-bound, find a matching auth token from the database. + /// If the above step is successful, and if requires_timestamp is given, the returned + /// AuthInfo will provide a Timestamp token as appropriate. + pub fn authorize_create( + &self, + purpose: KeyPurpose, + key_properties: Option<&(i64, Vec)>, + op_params: &[KmKeyParameter], + requires_timestamp: bool, + ) -> Result<(Option, AuthInfo)> { + let (key_id, key_params) = match key_properties { + Some((key_id, key_params)) => (*key_id, key_params), + None => { + return Ok(( + None, + AuthInfo { + state: DeferredAuthState::NoAuthRequired, + key_usage_limited: None, + confirmation_token_receiver: None, + }, + )); + } + }; + + match purpose { + // Allow SIGN, DECRYPT for both symmetric and asymmetric keys. + KeyPurpose::SIGN | KeyPurpose::DECRYPT => {} + // Rule out WRAP_KEY purpose + KeyPurpose::WRAP_KEY => { + return Err(Error::Km(Ec::INCOMPATIBLE_PURPOSE)) + .context(err!("WRAP_KEY purpose is not allowed here.",)); + } + // Allow AGREE_KEY for EC keys only. + KeyPurpose::AGREE_KEY => { + for kp in key_params.iter() { + if kp.get_tag() == Tag::ALGORITHM + && *kp.key_parameter_value() != KeyParameterValue::Algorithm(Algorithm::EC) + { + return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)) + .context(err!("key agreement is only supported for EC keys.",)); + } + } + } + KeyPurpose::VERIFY | KeyPurpose::ENCRYPT => { + // We do not support ENCRYPT and VERIFY (the remaining two options of purpose) for + // asymmetric keys. + for kp in key_params.iter() { + match *kp.key_parameter_value() { + KeyParameterValue::Algorithm(Algorithm::RSA) + | KeyParameterValue::Algorithm(Algorithm::EC) => { + return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context(err!( + "public operations on asymmetric keys are \ + not supported." + )); + } + _ => {} + } + } + } + _ => { + return Err(Error::Km(Ec::UNSUPPORTED_PURPOSE)).context(err!( + "authorize_create: specified purpose is not supported." + )); + } + } + // The following variables are to record information from key parameters to be used in + // enforcements, when two or more such pieces of information are required for enforcements. + // There is only one additional variable than what legacy keystore has, but this helps + // reduce the number of for loops on key parameters from 3 to 1, compared to legacy keystore + let mut key_purpose_authorized: bool = false; + let mut user_auth_type: Option = None; + let mut no_auth_required: bool = false; + let mut caller_nonce_allowed = false; + let mut user_id: i32 = -1; + let mut user_secure_ids = Vec::::new(); + let mut key_time_out: Option = None; + let mut unlocked_device_required = false; + let mut key_usage_limited: Option = None; + let mut confirmation_token_receiver: Option>>>>> = None; + let mut max_boot_level: Option = None; + + // iterate through key parameters, recording information we need for authorization + // enforcements later, or enforcing authorizations in place, where applicable + for key_param in key_params.iter() { + match key_param.key_parameter_value() { + KeyParameterValue::NoAuthRequired => { + no_auth_required = true; + } + KeyParameterValue::AuthTimeout(t) => { + key_time_out = Some(*t as i64); + } + KeyParameterValue::HardwareAuthenticatorType(a) => { + user_auth_type = Some(*a); + } + KeyParameterValue::KeyPurpose(p) => { + // The following check has the effect of key_params.contains(purpose) + // Also, authorizing purpose can not be completed here, if there can be multiple + // key parameters for KeyPurpose. + key_purpose_authorized = key_purpose_authorized || *p == purpose; + } + KeyParameterValue::CallerNonce => { + caller_nonce_allowed = true; + } + KeyParameterValue::ActiveDateTime(a) => { + if !Enforcements::is_given_time_passed(*a, true) { + return Err(Error::Km(Ec::KEY_NOT_YET_VALID)) + .context(err!("key is not yet active.")); + } + } + KeyParameterValue::OriginationExpireDateTime(o) => { + if (purpose == KeyPurpose::ENCRYPT || purpose == KeyPurpose::SIGN) + && Enforcements::is_given_time_passed(*o, false) + { + return Err(Error::Km(Ec::KEY_EXPIRED)).context(err!("key is expired.")); + } + } + KeyParameterValue::UsageExpireDateTime(u) => { + if (purpose == KeyPurpose::DECRYPT || purpose == KeyPurpose::VERIFY) + && Enforcements::is_given_time_passed(*u, false) + { + return Err(Error::Km(Ec::KEY_EXPIRED)).context(err!("key is expired.")); + } + } + KeyParameterValue::UserSecureID(s) => { + user_secure_ids.push(*s); + } + KeyParameterValue::UserID(u) => { + user_id = *u; + } + KeyParameterValue::UnlockedDeviceRequired => { + unlocked_device_required = true; + } + KeyParameterValue::UsageCountLimit(_) => { + // We don't examine the limit here because this is enforced on finish. + // Instead, we store the key_id so that finish can look up the key + // in the database again and check and update the counter. + key_usage_limited = Some(key_id); + } + KeyParameterValue::TrustedConfirmationRequired => { + confirmation_token_receiver = Some(self.confirmation_token_receiver.clone()); + } + KeyParameterValue::MaxBootLevel(level) => { + max_boot_level = Some(*level); + } + // NOTE: as per offline discussion, sanitizing key parameters and rejecting + // create operation if any non-allowed tags are present, is not done in + // authorize_create (unlike in legacy keystore where AuthorizeBegin is rejected if + // a subset of non-allowed tags are present). Because sanitizing key parameters + // should have been done during generate/import key, by KeyMint. + _ => { /*Do nothing on all the other key parameters, as in legacy keystore*/ } + } + } + + // authorize the purpose + if !key_purpose_authorized { + return Err(Error::Km(Ec::INCOMPATIBLE_PURPOSE)) + .context(err!("the purpose is not authorized.")); + } + + // if both NO_AUTH_REQUIRED and USER_SECURE_ID tags are present, return error + if !user_secure_ids.is_empty() && no_auth_required { + return Err(Error::Km(Ec::INVALID_KEY_BLOB)).context(err!( + "key has both NO_AUTH_REQUIRED and USER_SECURE_ID tags." + )); + } + + // if either of auth_type or secure_id is present and the other is not present, return error + if (user_auth_type.is_some() && user_secure_ids.is_empty()) + || (user_auth_type.is_none() && !user_secure_ids.is_empty()) + { + return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(err!( + "Auth required, but auth type {:?} + sids {:?} inconsistently specified", + user_auth_type, + user_secure_ids, + )); + } + + // validate caller nonce for origination purposes + if (purpose == KeyPurpose::ENCRYPT || purpose == KeyPurpose::SIGN) + && !caller_nonce_allowed + && op_params.iter().any(|kp| kp.tag == Tag::NONCE) + { + return Err(Error::Km(Ec::CALLER_NONCE_PROHIBITED)).context(err!( + "NONCE is present, although CALLER_NONCE is not present" + )); + } + + if unlocked_device_required { + // check the device locked status. If locked, operations on the key are not + // allowed. + if self.is_device_locked(user_id) { + return Err(Error::Km(Ec::DEVICE_LOCKED)).context(err!("device is locked.")); + } + } + + if let Some(level) = max_boot_level { + if !SUPER_KEY.read().unwrap().level_accessible(level) { + return Err(Error::Km(Ec::BOOT_LEVEL_EXCEEDED)) + .context(err!("boot level is too late.")); + } + } + + let (hat, state) = if user_secure_ids.is_empty() { + (None, DeferredAuthState::NoAuthRequired) + } else if let Some(key_time_out) = key_time_out { + let hat = Self::find_auth_token(|hat: &AuthTokenEntry| match user_auth_type { + Some(auth_type) => hat.satisfies(&user_secure_ids, auth_type), + None => false, // not reachable due to earlier check + }) + .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) + .context(err!( + "No suitable auth token for sids {:?} type {:?} received in last {}s found.", + user_secure_ids, + user_auth_type, + key_time_out + ))?; + let now = BootTime::now(); + let token_age = now + .checked_sub(&hat.time_received()) + .ok_or_else(Error::sys) + .context(err!( + "Overflow while computing Auth token validity. Validity cannot be established." + ))?; + + if token_age.seconds() > key_time_out { + return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(err!( + concat!( + "matching auth token (challenge={}, userId={}, authId={}, ", + "authType={:#x}, timestamp={}ms) rcved={:?} ", + "for sids {:?} type {:?} is expired ({}s old > timeout={}s)" + ), + hat.auth_token().challenge, + hat.auth_token().userId, + hat.auth_token().authenticatorId, + hat.auth_token().authenticatorType.0, + hat.auth_token().timestamp.milliSeconds, + hat.time_received(), + user_secure_ids, + user_auth_type, + token_age.seconds(), + key_time_out + )); + } + let state = if requires_timestamp { + DeferredAuthState::TimeStampRequired(hat.auth_token().clone()) + } else { + DeferredAuthState::NoAuthRequired + }; + (Some(hat.take_auth_token()), state) + } else { + (None, DeferredAuthState::OpAuthRequired) + }; + Ok(( + hat, + AuthInfo { + state, + key_usage_limited, + confirmation_token_receiver, + }, + )) + } + + fn find_auth_token(p: F) -> Option + where + F: Fn(&AuthTokenEntry) -> bool, + { + DB.with(|db| db.borrow().find_auth_token_entry(p)) + } + + /// Checks if the time now since epoch is greater than (or equal, if is_given_time_inclusive is + /// set) the given time (in milliseconds) + fn is_given_time_passed(given_time: i64, is_given_time_inclusive: bool) -> bool { + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH); + + let time_since_epoch = match duration_since_epoch { + Ok(duration) => duration.as_millis(), + Err(_) => return false, + }; + + if is_given_time_inclusive { + time_since_epoch >= (given_time as u128) + } else { + time_since_epoch > (given_time as u128) + } + } + + /// Check if the device is locked for the given user. If there's no entry yet for the user, + /// we assume that the device is locked + fn is_device_locked(&self, user_id: i32) -> bool { + let set = self.device_unlocked_set.lock().unwrap(); + !set.contains(&user_id) + } + + /// Sets the device locked status for the user. This method is called externally. + pub fn set_device_locked(&self, user_id: i32, device_locked_status: bool) { + let mut set = self.device_unlocked_set.lock().unwrap(); + if device_locked_status { + set.remove(&user_id); + } else { + set.insert(user_id); + } + } + + /// Add this auth token to the database. + /// Then present the auth token to the op auth map. If an operation is waiting for this + /// auth token this fulfills the request and removes the receiver from the map. + pub fn add_auth_token(&self, hat: HardwareAuthToken) { + DB.with(|db| db.borrow_mut().insert_auth_token(&hat)); + self.op_auth_map.add_auth_token(hat); + } + + /// This allows adding an entry to the op_auth_map, indexed by the operation challenge. + /// This is to be called by create_operation, once it has received the operation challenge + /// from keymint for an operation whose authorization decision is OpAuthRequired, as signalled + /// by the DeferredAuthState. + fn register_op_auth_receiver(&self, challenge: i64, recv: TokenReceiver) { + self.op_auth_map.add_receiver(challenge, recv); + } + + /// Given the set of key parameters and flags, check if super encryption is required. + pub fn super_encryption_required( + domain: &Domain, + key_parameters: &[KeyParameter], + flags: Option, + ) -> SuperEncryptionType { + if let Some(flags) = flags { + if (flags & KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING) != 0 { + return SuperEncryptionType::None; + } + } + // Each answer has a priority, numerically largest priority wins. + struct Candidate { + priority: u32, + enc_type: SuperEncryptionType, + } + let mut result = Candidate { + priority: 0, + enc_type: SuperEncryptionType::None, + }; + for kp in key_parameters { + let t = match kp.key_parameter_value() { + KeyParameterValue::MaxBootLevel(level) => Candidate { + priority: 3, + enc_type: SuperEncryptionType::BootLevel(*level), + }, + KeyParameterValue::UnlockedDeviceRequired if *domain == Domain::APP => Candidate { + priority: 2, + enc_type: SuperEncryptionType::UnlockedDeviceRequired, + }, + KeyParameterValue::UserSecureID(_) if *domain == Domain::APP => Candidate { + priority: 1, + enc_type: SuperEncryptionType::AfterFirstUnlock, + }, + _ => Candidate { + priority: 0, + enc_type: SuperEncryptionType::None, + }, + }; + if t.priority > result.priority { + result = t; + } + } + result.enc_type + } + + /// Finds a matching auth token along with a timestamp token. + /// This method looks through auth-tokens cached by keystore which satisfy the given + /// authentication information (i.e. |secureUserId|). + /// The most recent matching auth token which has a |challenge| field which matches + /// the passed-in |challenge| parameter is returned. + /// In this case the |authTokenMaxAgeMillis| parameter is not used. + /// + /// Otherwise, the most recent matching auth token which is younger than |authTokenMaxAgeMillis| + /// is returned. + pub fn get_auth_tokens( + &self, + challenge: i64, + secure_user_id: i64, + auth_token_max_age_millis: i64, + ) -> Result<(HardwareAuthToken, TimeStampToken)> { + let auth_type = HardwareAuthenticatorType::ANY; + let sids: Vec = vec![secure_user_id]; + // Filter the matching auth tokens by challenge + let result = Self::find_auth_token(|hat: &AuthTokenEntry| { + (challenge == hat.challenge()) && hat.satisfies(&sids, auth_type) + }); + + let auth_token = if let Some(auth_token_entry) = result { + auth_token_entry.take_auth_token() + } else { + // Filter the matching auth tokens by age. + if auth_token_max_age_millis != 0 { + let now_in_millis = BootTime::now(); + let result = Self::find_auth_token(|auth_token_entry: &AuthTokenEntry| { + let token_valid = now_in_millis + .checked_sub(&auth_token_entry.time_received()) + .map_or(false, |token_age_in_millis| { + auth_token_max_age_millis > token_age_in_millis.milliseconds() + }); + token_valid && auth_token_entry.satisfies(&sids, auth_type) + }); + + if let Some(auth_token_entry) = result { + auth_token_entry.take_auth_token() + } else { + return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) + .context(err!("No auth token found.")); + } + } else { + return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context(err!( + "No auth token found for \ + the given challenge and passed-in auth token max age is zero." + )); + } + }; + // Wait and obtain the timestamp token from secure clock service. + let tst = + get_timestamp_token(challenge).context(err!("Error in getting timestamp token."))?; + Ok((auth_token, tst)) + } + + /// Finds the most recent received time for an auth token that matches the given secure user id and authenticator + pub fn get_last_auth_time( + &self, + secure_user_id: i64, + auth_type: HardwareAuthenticatorType, + ) -> Option { + let sids: Vec = vec![secure_user_id]; + + let result = + Self::find_auth_token(|entry: &AuthTokenEntry| entry.satisfies(&sids, auth_type)); + + result.map(|auth_token_entry| auth_token_entry.time_received()) + } +} + +// TODO: Add tests to enforcement module (b/175578618). diff --git a/libs/rust/src/keymaster/error.rs b/libs/rust/src/keymaster/error.rs new file mode 100644 index 0000000..2466b9b --- /dev/null +++ b/libs/rust/src/keymaster/error.rs @@ -0,0 +1,54 @@ +use rsbinder::{ExceptionCode, Status, StatusCode}; + +use crate::android::{ + hardware::security::keymint::ErrorCode::ErrorCode, + system::keystore2::ResponseCode::ResponseCode, +}; + +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum KsError { + /// Wraps a Keystore `ResponseCode` as defined by the Keystore AIDL interface specification. + #[error("Error::Rc({0:?})")] + Rc(ResponseCode), + /// Wraps a Keymint `ErrorCode` as defined by the Keymint AIDL interface specification. + #[error("Error::Km({0:?})")] + Km(ErrorCode), + /// Wraps a Binder exception code other than a service specific exception. + #[error("Binder exception code {0:?}, {1:?}")] + Binder(ExceptionCode, i32), + /// Wraps a Binder status code. + #[error("Binder transaction error {0:?}")] + BinderTransaction(StatusCode), +} + +impl KsError { + /// Short hand for `Error::Rc(ResponseCode::SYSTEM_ERROR)` + pub fn sys() -> Self { + KsError::Rc(ResponseCode::SYSTEM_ERROR) + } + + /// Short hand for `Error::Rc(ResponseCode::PERMISSION_DENIED)` + pub fn perm() -> Self { + KsError::Rc(ResponseCode::PERMISSION_DENIED) + } +} + +/// This function is similar to map_km_error only that we don't expect +/// any KeyMint error codes, we simply preserve the exception code and optional +/// service specific exception. +pub fn map_binder_status(r: rsbinder::status::Result) -> Result { + match r { + Ok(t) => Ok(t), + Err(e) => Err(match e.exception_code() { + ExceptionCode::ServiceSpecific => { + let se = e.service_specific_error(); + KsError::Binder(ExceptionCode::ServiceSpecific, se) + } + ExceptionCode::TransactionFailed => { + let e = e.transaction_error(); + KsError::BinderTransaction(e) + } + e_code => KsError::Binder(e_code, 0), + }), + } +} diff --git a/libs/rust/src/keymaster/key_parameter.rs b/libs/rust/src/keymaster/key_parameter.rs new file mode 100644 index 0000000..3cef590 --- /dev/null +++ b/libs/rust/src/keymaster/key_parameter.rs @@ -0,0 +1,1100 @@ +// 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. + +//! Key parameters are declared by KeyMint to describe properties of keys and operations. +//! During key generation and import, key parameters are used to characterize a key, its usage +//! restrictions, and additional parameters for attestation. During the lifetime of the key, +//! the key characteristics are expressed as set of key parameters. During cryptographic +//! operations, clients may specify additional operation specific parameters. +//! This module provides a Keystore 2.0 internal representation for key parameters and +//! implements traits to convert it from and into KeyMint KeyParameters and store it in +//! the SQLite database. +//! +//! ## Synopsis +//! +//! enum KeyParameterValue { +//! Invalid, +//! Algorithm(Algorithm), +//! ... +//! } +//! +//! impl KeyParameterValue { +//! pub fn get_tag(&self) -> Tag; +//! pub fn new_from_sql(tag: Tag, data: &SqlField) -> Result; +//! pub fn new_from_tag_primitive_pair>(tag: Tag, v: T) +//! -> Result; +//! fn to_sql(&self) -> SqlResult +//! } +//! +//! use ...::keymint::KeyParameter as KmKeyParameter; +//! impl Into for KeyParameterValue {} +//! impl From for KeyParameterValue {} +//! +//! ## Implementation +//! Each of the six functions is implemented as match statement over each key parameter variant. +//! We bootstrap these function as well as the KeyParameterValue enum itself from a single list +//! of key parameters, that needs to be kept in sync with the KeyMint AIDL specification. +//! +//! The list resembles an enum declaration with a few extra fields. +//! enum KeyParameterValue { +//! Invalid with tag INVALID and field Invalid, +//! Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +//! ... +//! } +//! The tag corresponds to the variant of the keymint::Tag, and the field corresponds to the +//! variant of the keymint::KeyParameterValue union. There is no one to one mapping between +//! tags and union fields, e.g., the values of both tags BOOT_PATCHLEVEL and VENDOR_PATCHLEVEL +//! are stored in the Integer field. +//! +//! The macros interpreting them all follow a similar pattern and follow the following fragment +//! naming scheme: +//! +//! Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +//! $vname $(($vtype ))? with tag $tag_name and field $field_name, +//! +//! Further, KeyParameterValue appears in the macro as $enum_name. +//! Note that $vtype is optional to accommodate variants like Invalid which don't wrap a value. +//! +//! In some cases $vtype is not part of the expansion, but we still have to modify the expansion +//! depending on the presence of $vtype. In these cases we recurse through the list following the +//! following pattern: +//! +//! (@ , [], []) +//! +//! These macros usually have four rules: +//! * Two main recursive rules, of the form: +//! ( +//! @ +//! , +//! [], +//! [ ] +//! ) => { +//! macro!{@ , [ +//! +//! ], []} +//! }; +//! They pop one element off the and add one expansion to the out list. +//! The element expansion is kept on a separate line (or lines) for better readability. +//! The two variants differ in whether or not $vtype is expected. +//! * The termination condition which has an empty in list. +//! * The public interface, which does not have @marker and calls itself with an empty out list. + +use std::convert::TryInto; + +use crate::keymaster::database::utils::SqlField; +use crate::keymaster::error::KsError as KeystoreError; + +use crate::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin, + KeyParameter::KeyParameter as KmKeyParameter, + KeyParameterValue::KeyParameterValue as KmKeyParameterValue, KeyPurpose::KeyPurpose, + PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, Tag::Tag, +}; +use crate::android::system::keystore2::Authorization::Authorization; +use anyhow::{Context, Result}; +use rusqlite::types::{Null, ToSql, ToSqlOutput}; +use rusqlite::Result as SqlResult; +use serde::de::Deserializer; +use serde::ser::Serializer; +use serde::{Deserialize, Serialize}; + +/// This trait is used to associate a primitive to any type that can be stored inside a +/// KeyParameterValue, especially the AIDL enum types, e.g., keymint::{Algorithm, Digest, ...}. +/// This allows for simplifying the macro rules, e.g., for reading from the SQL database. +/// An expression like `KeyParameterValue::Algorithm(row.get(0))` would not work because +/// a type of `Algorithm` is expected which does not implement `FromSql` and we cannot +/// implement it because we own neither the type nor the trait. +/// With AssociatePrimitive we can write an expression +/// `KeyParameter::Algorithm(::from_primitive(row.get(0)))` to inform `get` +/// about the expected primitive type that it can convert into. By implementing this +/// trait for all inner types we can write a single rule to cover all cases (except where +/// there is no wrapped type): +/// `KeyParameterValue::$vname(<$vtype>::from_primitive(row.get(0)))` +trait AssociatePrimitive { + type Primitive: Into + TryFrom; + + fn from_primitive(v: Self::Primitive) -> Self; + fn to_primitive(&self) -> Self::Primitive; +} + +/// Associates the given type with i32. The macro assumes that the given type is actually a +/// tuple struct wrapping i32, such as AIDL enum types. +macro_rules! implement_associate_primitive_for_aidl_enum { + ($t:ty) => { + impl AssociatePrimitive for $t { + type Primitive = i32; + + fn from_primitive(v: Self::Primitive) -> Self { + Self(v) + } + fn to_primitive(&self) -> Self::Primitive { + self.0 + } + } + }; +} + +/// Associates the given type with itself. +macro_rules! implement_associate_primitive_identity { + ($t:ty) => { + impl AssociatePrimitive for $t { + type Primitive = $t; + + fn from_primitive(v: Self::Primitive) -> Self { + v + } + fn to_primitive(&self) -> Self::Primitive { + self.clone() + } + } + }; +} + +implement_associate_primitive_for_aidl_enum! {Algorithm} +implement_associate_primitive_for_aidl_enum! {BlockMode} +implement_associate_primitive_for_aidl_enum! {Digest} +implement_associate_primitive_for_aidl_enum! {EcCurve} +implement_associate_primitive_for_aidl_enum! {HardwareAuthenticatorType} +implement_associate_primitive_for_aidl_enum! {KeyOrigin} +implement_associate_primitive_for_aidl_enum! {KeyPurpose} +implement_associate_primitive_for_aidl_enum! {PaddingMode} +implement_associate_primitive_for_aidl_enum! {SecurityLevel} + +implement_associate_primitive_identity! {Vec} +implement_associate_primitive_identity! {i64} +implement_associate_primitive_identity! {i32} + +/// This enum allows passing a primitive value to `KeyParameterValue::new_from_tag_primitive_pair` +/// Usually, it is not necessary to use this type directly because the function uses +/// `Into` as a trait bound. +#[derive(Deserialize, Serialize)] +pub enum Primitive { + /// Wraps an i64. + I64(i64), + /// Wraps an i32. + I32(i32), + /// Wraps a Vec. + Vec(Vec), +} + +impl From for Primitive { + fn from(v: i64) -> Self { + Self::I64(v) + } +} +impl From for Primitive { + fn from(v: i32) -> Self { + Self::I32(v) + } +} +impl From> for Primitive { + fn from(v: Vec) -> Self { + Self::Vec(v) + } +} + +/// This error is returned by `KeyParameterValue::new_from_tag_primitive_pair`. +#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PrimitiveError { + /// Returned if this primitive is unsuitable for the given tag type. + #[error("Primitive does not match the expected tag type.")] + TypeMismatch, + /// Return if the tag type is unknown. + #[error("Unknown tag.")] + UnknownTag, +} + +impl TryFrom for i64 { + type Error = PrimitiveError; + + fn try_from(p: Primitive) -> Result { + match p { + Primitive::I64(v) => Ok(v), + _ => Err(Self::Error::TypeMismatch), + } + } +} +impl TryFrom for i32 { + type Error = PrimitiveError; + + fn try_from(p: Primitive) -> Result { + match p { + Primitive::I32(v) => Ok(v), + _ => Err(Self::Error::TypeMismatch), + } + } +} +impl TryFrom for Vec { + type Error = PrimitiveError; + + fn try_from(p: Primitive) -> Result, Self::Error> { + match p { + Primitive::Vec(v) => Ok(v), + _ => Err(Self::Error::TypeMismatch), + } + } +} + +fn serialize_primitive(v: &P, serializer: S) -> Result +where + S: Serializer, + P: AssociatePrimitive, +{ + let primitive: Primitive = v.to_primitive().into(); + primitive.serialize(serializer) +} + +fn deserialize_primitive<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: AssociatePrimitive, +{ + let primitive: Primitive = serde::de::Deserialize::deserialize(deserializer)?; + Ok(T::from_primitive( + primitive + .try_into() + .map_err(|_| serde::de::Error::custom("Type Mismatch"))?, + )) +} + +/// Expands the list of KeyParameterValue variants as follows: +/// +/// Input: +/// Invalid with tag INVALID and field Invalid, +/// Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +/// +/// Output: +/// ``` +/// pub fn new_from_tag_primitive_pair>( +/// tag: Tag, +/// v: T +/// ) -> Result { +/// let p: Primitive = v.into(); +/// Ok(match tag { +/// Tag::INVALID => KeyParameterValue::Invalid, +/// Tag::ALGORITHM => KeyParameterValue::Algorithm( +/// ::from_primitive(p.try_into()?) +/// ), +/// _ => return Err(PrimitiveError::UnknownTag), +/// }) +/// } +/// ``` +macro_rules! implement_from_tag_primitive_pair { + ($enum_name:ident; $($vname:ident$(($vtype:ty))? $tag_name:ident),*) => { + /// Returns the an instance of $enum_name or an error if the given primitive does not match + /// the tag type or the tag is unknown. + pub fn new_from_tag_primitive_pair>( + tag: Tag, + v: T + ) -> Result<$enum_name, PrimitiveError> { + let p: Primitive = v.into(); + Ok(match tag { + $(Tag::$tag_name => $enum_name::$vname$(( + <$vtype>::from_primitive(p.try_into()?) + ))?,)* + _ => return Err(PrimitiveError::UnknownTag), + }) + } + }; +} + +/// Expands the list of KeyParameterValue variants as follows: +/// +/// Input: +/// pub enum KeyParameterValue { +/// Invalid with tag INVALID and field Invalid, +/// Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +/// } +/// +/// Output: +/// ``` +/// pub enum KeyParameterValue { +/// Invalid, +/// Algorithm(Algorithm), +/// } +/// ``` +macro_rules! implement_enum { + ( + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $($(#[$emeta:meta])* $vname:ident$(($vtype:ty))?),* $(,)? + } + ) => { + $(#[$enum_meta])* + $enum_vis enum $enum_name { + $( + $(#[$emeta])* + $vname$(($vtype))? + ),* + } + }; +} + +/// Expands the list of KeyParameterValue variants as follows: +/// +/// Input: +/// Invalid with tag INVALID and field Invalid, +/// Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +/// +/// Output: +/// ``` +/// pub fn get_tag(&self) -> Tag { +/// match self { +/// KeyParameterValue::Invalid => Tag::INVALID, +/// KeyParameterValue::Algorithm(_) => Tag::ALGORITHM, +/// } +/// } +/// ``` +macro_rules! implement_get_tag { + ( + @replace_type_spec + $enum_name:ident, + [$($out:tt)*], + [$vname:ident($vtype:ty) $tag_name:ident, $($in:tt)*] + ) => { + implement_get_tag!{@replace_type_spec $enum_name, [$($out)* + $enum_name::$vname(_) => Tag::$tag_name, + ], [$($in)*]} + }; + ( + @replace_type_spec + $enum_name:ident, + [$($out:tt)*], + [$vname:ident $tag_name:ident, $($in:tt)*] + ) => { + implement_get_tag!{@replace_type_spec $enum_name, [$($out)* + $enum_name::$vname => Tag::$tag_name, + ], [$($in)*]} + }; + (@replace_type_spec $enum_name:ident, [$($out:tt)*], []) => { + /// Returns the tag of the given instance. + pub fn get_tag(&self) -> Tag { + match self { + $($out)* + } + } + }; + + ($enum_name:ident; $($vname:ident$(($vtype:ty))? $tag_name:ident),*) => { + implement_get_tag!{@replace_type_spec $enum_name, [], [$($vname$(($vtype))? $tag_name,)*]} + }; +} + +/// Expands the list of KeyParameterValue variants as follows: +/// +/// Input: +/// Invalid with tag INVALID and field Invalid, +/// Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +/// +/// Output: +/// ``` +/// fn to_sql(&self) -> SqlResult { +/// match self { +/// KeyParameterValue::Invalid => Ok(ToSqlOutput::from(Null)), +/// KeyParameterValue::Algorithm(v) => Ok(ToSqlOutput::from(v.to_primitive())), +/// } +/// } +/// ``` +macro_rules! implement_to_sql { + ( + @replace_type_spec + $enum_name:ident, + [$($out:tt)*], + [$vname:ident($vtype:ty), $($in:tt)*] + ) => { + implement_to_sql!{@replace_type_spec $enum_name, [ $($out)* + $enum_name::$vname(v) => Ok(ToSqlOutput::from(v.to_primitive())), + ], [$($in)*]} + }; + ( + @replace_type_spec + $enum_name:ident, + [$($out:tt)*], + [$vname:ident, $($in:tt)*] + ) => { + implement_to_sql!{@replace_type_spec $enum_name, [ $($out)* + $enum_name::$vname => Ok(ToSqlOutput::from(Null)), + ], [$($in)*]} + }; + (@replace_type_spec $enum_name:ident, [$($out:tt)*], []) => { + /// Converts $enum_name to be stored in a rusqlite database. + fn to_sql(&self) -> SqlResult { + match self { + $($out)* + } + } + }; + + + ($enum_name:ident; $($vname:ident$(($vtype:ty))?),*) => { + impl ToSql for $enum_name { + implement_to_sql!{@replace_type_spec $enum_name, [], [$($vname$(($vtype))?,)*]} + } + + } +} + +/// Expands the list of KeyParameterValue variants as follows: +/// +/// Input: +/// Invalid with tag INVALID and field Invalid, +/// Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +/// +/// Output: +/// ``` +/// pub fn new_from_sql( +/// tag: Tag, +/// data: &SqlField, +/// ) -> Result { +/// Ok(match self { +/// Tag::Invalid => KeyParameterValue::Invalid, +/// Tag::ALGORITHM => { +/// KeyParameterValue::Algorithm(::from_primitive(data +/// .get() +/// .map_err(|_| KeystoreError::Rc(ResponseCode::VALUE_CORRUPTED)) +/// .context(concat!("Failed to read sql data for tag: ", "ALGORITHM", "."))? +/// )) +/// }, +/// }) +/// } +/// ``` +macro_rules! implement_new_from_sql { + ($enum_name:ident; $($vname:ident$(($vtype:ty))? $tag_name:ident),*) => { + /// Takes a tag and an SqlField and attempts to construct a KeyParameter value. + /// This function may fail if the parameter value cannot be extracted from the + /// database cell. + pub fn new_from_sql( + tag: Tag, + data: &SqlField, + ) -> Result { + Ok(match tag { + $( + Tag::$tag_name => { + $enum_name::$vname$((<$vtype>::from_primitive(data + .get() + .map_err(|_| KeystoreError::Rc(crate::android::system::keystore2::ResponseCode::ResponseCode::VALUE_CORRUPTED)) + .context(concat!( + "Failed to read sql data for tag: ", + stringify!($tag_name), + "." + ))? + )))? + }, + )* + _ => $enum_name::Invalid, + }) + } + }; +} + +/// This key parameter default is used during the conversion from KeyParameterValue +/// to keymint::KeyParameterValue. Keystore's version does not have wrapped types +/// for boolean tags and the tag Invalid. The AIDL version uses bool and integer +/// variants respectively. This default function is invoked in these cases to +/// homogenize the rules for boolean and invalid tags. +/// The bool variant returns true because boolean parameters are implicitly true +/// if present. +trait KpDefault { + fn default() -> Self; +} + +impl KpDefault for i32 { + fn default() -> Self { + 0 + } +} + +impl KpDefault for bool { + fn default() -> Self { + true + } +} + +/// Expands the list of KeyParameterValue variants as follows: +/// +/// Input: +/// Invalid with tag INVALID and field Invalid, +/// Algorithm(Algorithm) with tag ALGORITHM and field Algorithm, +/// +/// Output: +/// ``` +/// impl From for KeyParameterValue { +/// fn from(kp: KmKeyParameter) -> Self { +/// match kp { +/// KmKeyParameter { tag: Tag::INVALID, value: KmKeyParameterValue::Invalid(_) } +/// => $enum_name::$vname, +/// KmKeyParameter { tag: Tag::Algorithm, value: KmKeyParameterValue::Algorithm(v) } +/// => $enum_name::Algorithm(v), +/// _ => $enum_name::Invalid, +/// } +/// } +/// } +/// +/// impl Into for KeyParameterValue { +/// fn into(self) -> KmKeyParameter { +/// match self { +/// KeyParameterValue::Invalid => KmKeyParameter { +/// tag: Tag::INVALID, +/// value: KmKeyParameterValue::Invalid(KpDefault::default()) +/// }, +/// KeyParameterValue::Algorithm(v) => KmKeyParameter { +/// tag: Tag::ALGORITHM, +/// value: KmKeyParameterValue::Algorithm(v) +/// }, +/// } +/// } +/// } +/// ``` +macro_rules! implement_try_from_to_km_parameter { + // The first three rules expand From. + ( + @from + $enum_name:ident, + [$($out:tt)*], + [$vname:ident($vtype:ty) $tag_name:ident $field_name:ident, $($in:tt)*] + ) => { + implement_try_from_to_km_parameter!{@from $enum_name, [$($out)* + KmKeyParameter { + tag: Tag::$tag_name, + value: KmKeyParameterValue::$field_name(v) + } => $enum_name::$vname(v), + ], [$($in)*] + }}; + ( + @from + $enum_name:ident, + [$($out:tt)*], + [$vname:ident $tag_name:ident $field_name:ident, $($in:tt)*] + ) => { + implement_try_from_to_km_parameter!{@from $enum_name, [$($out)* + KmKeyParameter { + tag: Tag::$tag_name, + value: KmKeyParameterValue::$field_name(_) + } => $enum_name::$vname, + ], [$($in)*] + }}; + (@from $enum_name:ident, [$($out:tt)*], []) => { + impl From for $enum_name { + fn from(kp: KmKeyParameter) -> Self { + match kp { + $($out)* + _ => $enum_name::Invalid, + } + } + } + }; + + // The next three rules expand Into. + ( + @into + $enum_name:ident, + [$($out:tt)*], + [$vname:ident($vtype:ty) $tag_name:ident $field_name:ident, $($in:tt)*] + ) => { + implement_try_from_to_km_parameter!{@into $enum_name, [$($out)* + $enum_name::$vname(v) => KmKeyParameter { + tag: Tag::$tag_name, + value: KmKeyParameterValue::$field_name(v) + }, + ], [$($in)*] + }}; + ( + @into + $enum_name:ident, + [$($out:tt)*], + [$vname:ident $tag_name:ident $field_name:ident, $($in:tt)*] + ) => { + implement_try_from_to_km_parameter!{@into $enum_name, [$($out)* + $enum_name::$vname => KmKeyParameter { + tag: Tag::$tag_name, + value: KmKeyParameterValue::$field_name(KpDefault::default()) + }, + ], [$($in)*] + }}; + (@into $enum_name:ident, [$($out:tt)*], []) => { + impl From<$enum_name> for KmKeyParameter { + fn from(x: $enum_name) -> Self { + match x { + $($out)* + } + } + } + }; + + + ($enum_name:ident; $($vname:ident$(($vtype:ty))? $tag_name:ident $field_name:ident),*) => { + implement_try_from_to_km_parameter!( + @from $enum_name, + [], + [$($vname$(($vtype))? $tag_name $field_name,)*] + ); + implement_try_from_to_km_parameter!( + @into $enum_name, + [], + [$($vname$(($vtype))? $tag_name $field_name,)*] + ); + }; +} + +/// This is the top level macro. While the other macros do most of the heavy lifting, this takes +/// the key parameter list and passes it on to the other macros to generate all of the conversion +/// functions. In addition, it generates an important test vector for verifying that tag type of the +/// keymint tag matches the associated keymint KeyParameterValue field. +macro_rules! implement_key_parameter_value { + ( + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $( + $(#[$($emeta:tt)+])* + $vname:ident$(($vtype:ty))? + ),* $(,)? + } + ) => { + implement_key_parameter_value!{ + @extract_attr + $(#[$enum_meta])* + $enum_vis enum $enum_name { + [] + [$( + [] [$(#[$($emeta)+])*] + $vname$(($vtype))?, + )*] + } + } + }; + + ( + @extract_attr + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + [$($out:tt)*] + [ + [$(#[$mout:meta])*] + [ + #[key_param(tag = $tag_name:ident, field = $field_name:ident)] + $(#[$($mtail:tt)+])* + ] + $vname:ident$(($vtype:ty))?, + $($tail:tt)* + ] + } + ) => { + implement_key_parameter_value!{ + @extract_attr + $(#[$enum_meta])* + $enum_vis enum $enum_name { + [ + $($out)* + $(#[$mout])* + $(#[$($mtail)+])* + $tag_name $field_name $vname$(($vtype))?, + ] + [$($tail)*] + } + } + }; + + ( + @extract_attr + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + [$($out:tt)*] + [ + [$(#[$mout:meta])*] + [ + #[$front:meta] + $(#[$($mtail:tt)+])* + ] + $vname:ident$(($vtype:ty))?, + $($tail:tt)* + ] + } + ) => { + implement_key_parameter_value!{ + @extract_attr + $(#[$enum_meta])* + $enum_vis enum $enum_name { + [$($out)*] + [ + [ + $(#[$mout])* + #[$front] + ] + [$(#[$($mtail)+])*] + $vname$(($vtype))?, + $($tail)* + ] + } + } + }; + + ( + @extract_attr + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + [$($out:tt)*] + [] + } + ) => { + implement_key_parameter_value!{ + @spill + $(#[$enum_meta])* + $enum_vis enum $enum_name { + $($out)* + } + } + }; + + ( + @spill + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $( + $(#[$emeta:meta])* + $tag_name:ident $field_name:ident $vname:ident$(($vtype:ty))?, + )* + } + ) => { + implement_enum!( + $(#[$enum_meta])* + $enum_vis enum $enum_name { + $( + $(#[$emeta])* + $vname$(($vtype))? + ),* + }); + + impl $enum_name { + implement_new_from_sql!($enum_name; $($vname$(($vtype))? $tag_name),*); + implement_get_tag!($enum_name; $($vname$(($vtype))? $tag_name),*); + implement_from_tag_primitive_pair!($enum_name; $($vname$(($vtype))? $tag_name),*); + + #[cfg(test)] + fn make_field_matches_tag_type_test_vector() -> Vec { + vec![$(KmKeyParameter{ + tag: Tag::$tag_name, + value: KmKeyParameterValue::$field_name(Default::default())} + ),*] + } + + #[cfg(test)] + fn make_key_parameter_defaults_vector() -> Vec { + vec![$(KeyParameter{ + value: KeyParameterValue::$vname$((<$vtype as Default>::default()))?, + security_level: SecurityLevel(100), + }),*] + } + } + + implement_try_from_to_km_parameter!( + $enum_name; + $($vname$(($vtype))? $tag_name $field_name),* + ); + + implement_to_sql!($enum_name; $($vname$(($vtype))?),*); + }; +} + +implement_key_parameter_value! { +/// KeyParameterValue holds a value corresponding to one of the Tags defined in +/// the AIDL spec at hardware/interfaces/security/keymint +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)] +pub enum KeyParameterValue { + /// Associated with Tag:INVALID + #[key_param(tag = INVALID, field = Invalid)] + Invalid, + /// Set of purposes for which the key may be used + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = PURPOSE, field = KeyPurpose)] + KeyPurpose(KeyPurpose), + /// Cryptographic algorithm with which the key is used + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = ALGORITHM, field = Algorithm)] + Algorithm(Algorithm), + /// Size of the key , in bits + #[key_param(tag = KEY_SIZE, field = Integer)] + KeySize(i32), + /// Block cipher mode(s) with which the key may be used + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = BLOCK_MODE, field = BlockMode)] + BlockMode(BlockMode), + /// Digest algorithms that may be used with the key to perform signing and verification + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = DIGEST, field = Digest)] + Digest(Digest), + /// Digest algorithms that can be used for MGF in RSA-OAEP. + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = RSA_OAEP_MGF_DIGEST, field = Digest)] + RsaOaepMgfDigest(Digest), + /// Padding modes that may be used with the key. Relevant to RSA, AES and 3DES keys. + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = PADDING, field = PaddingMode)] + PaddingMode(PaddingMode), + /// Can the caller provide a nonce for nonce-requiring operations + #[key_param(tag = CALLER_NONCE, field = BoolValue)] + CallerNonce, + /// Minimum length of MAC for HMAC keys and AES keys that support GCM mode + #[key_param(tag = MIN_MAC_LENGTH, field = Integer)] + MinMacLength(i32), + /// The elliptic curve + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = EC_CURVE, field = EcCurve)] + EcCurve(EcCurve), + /// Value of the public exponent for an RSA key pair + #[key_param(tag = RSA_PUBLIC_EXPONENT, field = LongInteger)] + RSAPublicExponent(i64), + /// An attestation certificate for the generated key should contain an application-scoped + /// and time-bounded device-unique ID + #[key_param(tag = INCLUDE_UNIQUE_ID, field = BoolValue)] + IncludeUniqueID, + //TODO: find out about this + // /// Necessary system environment conditions for the generated key to be used + // KeyBlobUsageRequirements(KeyBlobUsageRequirements), + /// Only the boot loader can use the key + #[key_param(tag = BOOTLOADER_ONLY, field = BoolValue)] + BootLoaderOnly, + /// When deleted, the key is guaranteed to be permanently deleted and unusable + #[key_param(tag = ROLLBACK_RESISTANCE, field = BoolValue)] + RollbackResistance, + /// The Key shall only be used during the early boot stage + #[key_param(tag = EARLY_BOOT_ONLY, field = BoolValue)] + EarlyBootOnly, + /// The date and time at which the key becomes active + #[key_param(tag = ACTIVE_DATETIME, field = DateTime)] + ActiveDateTime(i64), + /// The date and time at which the key expires for signing and encryption + #[key_param(tag = ORIGINATION_EXPIRE_DATETIME, field = DateTime)] + OriginationExpireDateTime(i64), + /// The date and time at which the key expires for verification and decryption + #[key_param(tag = USAGE_EXPIRE_DATETIME, field = DateTime)] + UsageExpireDateTime(i64), + /// Minimum amount of time that elapses between allowed operations + #[key_param(tag = MIN_SECONDS_BETWEEN_OPS, field = Integer)] + MinSecondsBetweenOps(i32), + /// Maximum number of times that a key may be used between system reboots + #[key_param(tag = MAX_USES_PER_BOOT, field = Integer)] + MaxUsesPerBoot(i32), + /// The number of times that a limited use key can be used + #[key_param(tag = USAGE_COUNT_LIMIT, field = Integer)] + UsageCountLimit(i32), + /// ID of the Android user that is permitted to use the key + #[key_param(tag = USER_ID, field = Integer)] + UserID(i32), + /// A key may only be used under a particular secure user authentication state + #[key_param(tag = USER_SECURE_ID, field = LongInteger)] + UserSecureID(i64), + /// No authentication is required to use this key + #[key_param(tag = NO_AUTH_REQUIRED, field = BoolValue)] + NoAuthRequired, + /// The types of user authenticators that may be used to authorize this key + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = USER_AUTH_TYPE, field = HardwareAuthenticatorType)] + HardwareAuthenticatorType(HardwareAuthenticatorType), + /// The time in seconds for which the key is authorized for use, after user authentication + #[key_param(tag = AUTH_TIMEOUT, field = Integer)] + AuthTimeout(i32), + /// The key's authentication timeout, if it has one, is automatically expired when the device is + /// removed from the user's body. No longer implemented; this tag is no longer enforced. + #[key_param(tag = ALLOW_WHILE_ON_BODY, field = BoolValue)] + AllowWhileOnBody, + /// The key must be unusable except when the user has provided proof of physical presence + #[key_param(tag = TRUSTED_USER_PRESENCE_REQUIRED, field = BoolValue)] + TrustedUserPresenceRequired, + /// Applicable to keys with KeyPurpose SIGN, and specifies that this key must not be usable + /// unless the user provides confirmation of the data to be signed + #[key_param(tag = TRUSTED_CONFIRMATION_REQUIRED, field = BoolValue)] + TrustedConfirmationRequired, + /// The key may only be used when the device is unlocked + #[key_param(tag = UNLOCKED_DEVICE_REQUIRED, field = BoolValue)] + UnlockedDeviceRequired, + /// When provided to generateKey or importKey, this tag specifies data + /// that is necessary during all uses of the key + #[key_param(tag = APPLICATION_ID, field = Blob)] + ApplicationID(Vec), + /// When provided to generateKey or importKey, this tag specifies data + /// that is necessary during all uses of the key + #[key_param(tag = APPLICATION_DATA, field = Blob)] + ApplicationData(Vec), + /// Specifies the date and time the key was created + #[key_param(tag = CREATION_DATETIME, field = DateTime)] + CreationDateTime(i64), + /// Specifies where the key was created, if known + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + #[key_param(tag = ORIGIN, field = Origin)] + KeyOrigin(KeyOrigin), + /// The key used by verified boot to validate the operating system booted + #[key_param(tag = ROOT_OF_TRUST, field = Blob)] + RootOfTrust(Vec), + /// System OS version with which the key may be used + #[key_param(tag = OS_VERSION, field = Integer)] + OSVersion(i32), + /// Specifies the system security patch level with which the key may be used + #[key_param(tag = OS_PATCHLEVEL, field = Integer)] + OSPatchLevel(i32), + /// Specifies a unique, time-based identifier + #[key_param(tag = UNIQUE_ID, field = Blob)] + UniqueID(Vec), + /// Used to deliver a "challenge" value to the attestKey() method + #[key_param(tag = ATTESTATION_CHALLENGE, field = Blob)] + AttestationChallenge(Vec), + /// The set of applications which may use a key, used only with attestKey() + #[key_param(tag = ATTESTATION_APPLICATION_ID, field = Blob)] + AttestationApplicationID(Vec), + /// Provides the device's brand name, to attestKey() + #[key_param(tag = ATTESTATION_ID_BRAND, field = Blob)] + AttestationIdBrand(Vec), + /// Provides the device's device name, to attestKey() + #[key_param(tag = ATTESTATION_ID_DEVICE, field = Blob)] + AttestationIdDevice(Vec), + /// Provides the device's product name, to attestKey() + #[key_param(tag = ATTESTATION_ID_PRODUCT, field = Blob)] + AttestationIdProduct(Vec), + /// Provides the device's serial number, to attestKey() + #[key_param(tag = ATTESTATION_ID_SERIAL, field = Blob)] + AttestationIdSerial(Vec), + /// Provides the primary IMEI for the device, to attestKey() + #[key_param(tag = ATTESTATION_ID_IMEI, field = Blob)] + AttestationIdIMEI(Vec), + /// Provides a second IMEI for the device, to attestKey() + #[key_param(tag = ATTESTATION_ID_SECOND_IMEI, field = Blob)] + AttestationIdSecondIMEI(Vec), + /// Provides the MEIDs for all radios on the device, to attestKey() + #[key_param(tag = ATTESTATION_ID_MEID, field = Blob)] + AttestationIdMEID(Vec), + /// Provides the device's manufacturer name, to attestKey() + #[key_param(tag = ATTESTATION_ID_MANUFACTURER, field = Blob)] + AttestationIdManufacturer(Vec), + /// Provides the device's model name, to attestKey() + #[key_param(tag = ATTESTATION_ID_MODEL, field = Blob)] + AttestationIdModel(Vec), + /// Specifies the vendor image security patch level with which the key may be used + #[key_param(tag = VENDOR_PATCHLEVEL, field = Integer)] + VendorPatchLevel(i32), + /// Specifies the boot image (kernel) security patch level with which the key may be used + #[key_param(tag = BOOT_PATCHLEVEL, field = Integer)] + BootPatchLevel(i32), + /// Provides "associated data" for AES-GCM encryption or decryption + #[key_param(tag = ASSOCIATED_DATA, field = Blob)] + AssociatedData(Vec), + /// Provides or returns a nonce or Initialization Vector (IV) for AES-GCM, + /// AES-CBC, AES-CTR, or 3DES-CBC encryption or decryption + #[key_param(tag = NONCE, field = Blob)] + Nonce(Vec), + /// Provides the requested length of a MAC or GCM authentication tag, in bits + #[key_param(tag = MAC_LENGTH, field = Integer)] + MacLength(i32), + /// Specifies whether the device has been factory reset since the + /// last unique ID rotation. Used for key attestation + #[key_param(tag = RESET_SINCE_ID_ROTATION, field = BoolValue)] + ResetSinceIdRotation, + /// Used to deliver a cryptographic token proving that the user + /// confirmed a signing request + #[key_param(tag = CONFIRMATION_TOKEN, field = Blob)] + ConfirmationToken(Vec), + /// Used to deliver the certificate serial number to the KeyMint instance + /// certificate generation. + #[key_param(tag = CERTIFICATE_SERIAL, field = Blob)] + CertificateSerial(Vec), + /// Used to deliver the certificate subject to the KeyMint instance + /// certificate generation. This must be DER encoded X509 name. + #[key_param(tag = CERTIFICATE_SUBJECT, field = Blob)] + CertificateSubject(Vec), + /// Used to deliver the not before date in milliseconds to KeyMint during key generation/import. + #[key_param(tag = CERTIFICATE_NOT_BEFORE, field = DateTime)] + CertificateNotBefore(i64), + /// Used to deliver the not after date in milliseconds to KeyMint during key generation/import. + #[key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)] + CertificateNotAfter(i64), + /// Specifies a maximum boot level at which a key should function + #[key_param(tag = MAX_BOOT_LEVEL, field = Integer)] + MaxBootLevel(i32), +} +} + +impl From<&KmKeyParameter> for KeyParameterValue { + fn from(kp: &KmKeyParameter) -> Self { + kp.clone().into() + } +} + +/// KeyParameter wraps the KeyParameterValue and the security level at which it is enforced. +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct KeyParameter { + value: KeyParameterValue, + #[serde(deserialize_with = "deserialize_primitive")] + #[serde(serialize_with = "serialize_primitive")] + security_level: SecurityLevel, +} + +impl KeyParameter { + /// Create an instance of KeyParameter, given the value and the security level. + pub fn new(value: KeyParameterValue, security_level: SecurityLevel) -> Self { + KeyParameter { + value, + security_level, + } + } + + /// Construct a KeyParameter from the data from a rusqlite row. + /// Note that following variants of KeyParameterValue should not be stored: + /// IncludeUniqueID, ApplicationID, ApplicationData, RootOfTrust, UniqueID, + /// Attestation*, AssociatedData, Nonce, MacLength, ResetSinceIdRotation, ConfirmationToken. + /// This filtering is enforced at a higher level and here we support conversion for all the + /// variants. + pub fn new_from_sql( + tag_val: Tag, + data: &SqlField, + security_level_val: SecurityLevel, + ) -> Result { + Ok(Self { + value: KeyParameterValue::new_from_sql(tag_val, data)?, + security_level: security_level_val, + }) + } + + /// Get the KeyMint Tag of this this key parameter. + pub fn get_tag(&self) -> Tag { + self.value.get_tag() + } + + /// Returns key parameter value. + pub fn key_parameter_value(&self) -> &KeyParameterValue { + &self.value + } + + /// Returns the security level of this key parameter. + pub fn security_level(&self) -> &SecurityLevel { + &self.security_level + } + + /// An authorization is a KeyParameter with an associated security level that is used + /// to convey the key characteristics to keystore clients. This function consumes + /// an internal KeyParameter representation to produce the Authorization wire type. + pub fn into_authorization(self) -> Authorization { + Authorization { + securityLevel: self.security_level, + keyParameter: self.value.into(), + } + } +} diff --git a/libs/rust/src/keymaster/keymint_device.rs b/libs/rust/src/keymaster/keymint_device.rs new file mode 100644 index 0000000..d616b58 --- /dev/null +++ b/libs/rust/src/keymaster/keymint_device.rs @@ -0,0 +1,506 @@ +// Copyright 2021, 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. + +//! Provide the [`KeyMintDevice`] wrapper for operating directly on a KeyMint device. + +use std::sync::{Arc, OnceLock}; + +use crate::android::hardware::security::keymint::{ + HardwareAuthToken::HardwareAuthToken, IKeyMintDevice::IKeyMintDevice, + IKeyMintOperation::IKeyMintOperation, KeyCharacteristics::KeyCharacteristics, + KeyCreationResult::KeyCreationResult, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, + SecurityLevel::SecurityLevel, +}; +use crate::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, +}; +use crate::global::AID_KEYSTORE; +use crate::keymint::{clock, sdd, soft}; +use crate::{ + android::hardware::security::keymint::ErrorCode::ErrorCode, + err, + keymaster::{ + db::{ + BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, + KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, + KeymasterDb as KeystoreDB, SubComponentType, + }, + error::KsError as Error, + super_key::KeyBlob, + }, + watchdog as wd, +}; +use anyhow::{anyhow, Context, Result}; +use kmr_crypto_boring::ec::BoringEc; +use kmr_crypto_boring::hmac::BoringHmac; +use kmr_crypto_boring::rng::BoringRng; +use kmr_crypto_boring::rsa::BoringRsa; +use kmr_ta::device::CsrSigningAlgorithm; +use kmr_ta::{HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; +use kmr_wire::keymint::KeyMintHardwareInfo; +use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; +use log::error; +use rsbinder::Strong; + +/// Wrapper for operating directly on a KeyMint device. +/// These methods often mirror methods in [`crate::security_level`]. However +/// the functions in [`crate::security_level`] make assumptions that hold, and has side effects +/// that make sense, only if called by an external client through binder. +/// In addition we are trying to maintain a separation between interface services +/// so that the architecture is compatible with a future move to multiple thread pools. +/// So the simplest approach today is to write new implementations of them for internal use. +/// Because these methods run very early, we don't even try to cooperate with +/// the operation slot database; we assume there will be plenty of slots. +pub struct KeyMintDevice { + km_dev: KeyMintWrapper, + version: i32, + security_level: SecurityLevel, +} + +impl KeyMintDevice { + /// Version number of KeyMasterDevice@V4_0 + pub const KEY_MASTER_V4_0: i32 = 40; + /// Version number of KeyMasterDevice@V4_1 + pub const KEY_MASTER_V4_1: i32 = 41; + /// Version number of KeyMintDevice@V1 + pub const KEY_MINT_V1: i32 = 100; + /// Version number of KeyMintDevice@V2 + pub const KEY_MINT_V2: i32 = 200; + /// Version number of KeyMintDevice@V3 + pub const KEY_MINT_V3: i32 = 300; + + /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] + pub fn get(security_level: SecurityLevel) -> Result { + let (km_dev, hw_info) = + get_keymint_device(security_level).context(err!("get_keymint_device failed"))?; + + Ok(KeyMintDevice { + km_dev, + version: hw_info.version_number, + security_level: get_keymaster_security_level(hw_info.security_level)?, + }) + } + + /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`], return + /// [`None`] if the error `HARDWARE_TYPE_UNAVAILABLE` is returned + pub fn get_or_none(security_level: SecurityLevel) -> Result> { + KeyMintDevice::get(security_level).map(Some).or_else(|e| { + match e.root_cause().downcast_ref::() { + Some(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) => Ok(None), + _ => Err(e), + } + }) + } + + /// Returns the version of the underlying KeyMint/KeyMaster device. + pub fn version(&self) -> i32 { + self.version + } + + /// Returns the self advertised security level of the KeyMint device. + /// This may differ from the requested security level if the best security level + /// on the device is Software. + pub fn security_level(&self) -> SecurityLevel { + self.security_level + } + + /// Create a KM key and store in the database. + pub fn create_and_store_key( + &self, + db: &mut KeystoreDB, + key_desc: &KeyDescriptor, + key_type: KeyType, + creator: F, + ) -> Result<()> + where + F: FnOnce(KeyMintWrapper) -> Result, + { + let creation_result = creator(self.km_dev).context(err!("creator failed"))?; + let key_parameters = crate::keymaster::utils::key_characteristics_to_internal( + creation_result.keyCharacteristics, + ); + + let creation_date = DateTime::now().context(err!("DateTime::now() failed"))?; + + let mut key_metadata = KeyMetaData::new(); + key_metadata.add(KeyMetaEntry::CreationDate(creation_date)); + + db.store_new_key( + key_desc, + key_type, + &key_parameters, + &BlobInfo::new(&creation_result.keyBlob, &blob_metadata), + &CertificateInfo::new(None, None), + &key_metadata, + ) + .context(err!("store_new_key failed"))?; + Ok(()) + } + + /// Generate a KeyDescriptor for internal-use keys. + pub fn internal_descriptor(alias: String) -> KeyDescriptor { + KeyDescriptor { + domain: Domain::APP, + nspace: AID_KEYSTORE as i64, + alias: Some(alias), + blob: None, + } + } + + /// Look up an internal-use key in the database given a key descriptor. + fn lookup_from_desc( + db: &mut KeystoreDB, + key_desc: &KeyDescriptor, + key_type: KeyType, + ) -> Result<(KeyIdGuard, KeyEntry)> { + db.load_key_entry(key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE) + .context(err!("load_key_entry failed.")) + } + + /// Look up the key in the database, and return None if it is absent. + fn not_found_is_none( + lookup: Result<(KeyIdGuard, KeyEntry)>, + ) -> Result> { + match lookup { + Ok(result) => Ok(Some(result)), + Err(e) => match e.root_cause().downcast_ref::() { + Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None), + _ => Err(e), + }, + } + } + + /// This does the lookup and store in separate transactions; caller must + /// hold a lock before calling. + pub fn lookup_or_generate_key( + &self, + db: &mut KeystoreDB, + key_desc: &KeyDescriptor, + key_type: KeyType, + params: &[KeyParameter], + validate_characteristics: F, + ) -> Result<(KeyIdGuard, KeyBlob)> + where + F: FnOnce(&[KeyCharacteristics]) -> bool, + { + // We use a separate transaction for the lookup than for the store + // - to keep the code simple + // - because the caller needs to hold a lock in any case + // - because it avoids holding database locks during slow + // KeyMint operations + let lookup = Self::not_found_is_none(Self::lookup_from_desc(db, key_desc, key_type)) + .context(err!("first lookup failed"))?; + + if let Some((key_id_guard, mut key_entry)) = lookup { + // If the key is associated with a different km instance + // or if there is no blob metadata for some reason the key entry + // is considered corrupted and needs to be replaced with a new one. + let key_blob = key_entry + .take_key_blob_info() + .and_then(|(key_blob, blob_metadata)| Some(key_blob)); + + if let Some(key_blob_vec) = key_blob { + let (key_characteristics, key_blob) = self + .upgrade_keyblob_if_required_with( + db, + &key_id_guard, + KeyBlob::NonSensitive(key_blob_vec), + |key_blob| { + let _wp = wd::watch(concat!( + "KeyMintDevice::lookup_or_generate_key: ", + "calling IKeyMintDevice::getKeyCharacteristics." + )); + self.km_dev.getKeyCharacteristics(key_blob, &[], &[]) + }, + ) + .context(err!("calling getKeyCharacteristics"))?; + + if validate_characteristics(&key_characteristics) { + return Ok((key_id_guard, key_blob)); + } + + // If this point is reached the existing key is considered outdated or corrupted + // in some way. It will be replaced with a new key below. + }; + } + + self.create_and_store_key(db, key_desc, key_type, |km_dev| { + km_dev.generateKey(params, None) + }) + .context(err!("generate_and_store_key failed"))?; + Self::lookup_from_desc(db, key_desc, key_type) + .and_then(|(key_id_guard, mut key_entry)| { + Ok(( + key_id_guard, + key_entry + .take_key_blob_info() + .ok_or(Error::Rc(ResponseCode::KEY_NOT_FOUND)) + .map(|(key_blob, _)| KeyBlob::NonSensitive(key_blob)) + .context(err!("Missing key blob info."))?, + )) + }) + .context(err!("second lookup failed")) + } + + /// Call the passed closure; if it returns `KEY_REQUIRES_UPGRADE`, call upgradeKey, and + /// write the upgraded key to the database. + fn upgrade_keyblob_if_required_with<'a, T, F>( + &self, + db: &mut KeystoreDB, + key_id_guard: &KeyIdGuard, + key_blob: KeyBlob<'a>, + f: F, + ) -> Result<(T, KeyBlob<'a>)> + where + F: Fn(&[u8]) -> Result, + { + let (f_result, upgraded_blob) = crate::keymaster::utils::upgrade_keyblob_if_required_with( + &self.km_dev, + self.version(), + &key_blob, + &[], + f, + |upgraded_blob| { + let mut new_blob_metadata = BlobMetaData::new(); + new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); + + db.set_blob( + key_id_guard, + SubComponentType::KEY_BLOB, + Some(upgraded_blob), + Some(&new_blob_metadata), + ) + .context(err!("Failed to insert upgraded blob into the database"))?; + Ok(()) + }, + )?; + let returned_blob = match upgraded_blob { + None => key_blob, + Some(upgraded_blob) => KeyBlob::NonSensitive(upgraded_blob), + }; + Ok((f_result, returned_blob)) + } + + /// Use the created key in an operation that can be done with + /// a call to begin followed by a call to finish. + pub fn use_key_in_one_step( + &self, + db: &mut KeystoreDB, + key_id_guard: &KeyIdGuard, + key_blob: &[u8], + purpose: KeyPurpose, + operation_parameters: &[KeyParameter], + auth_token: Option<&HardwareAuthToken>, + input: &[u8], + ) -> Result> { + let key_blob = KeyBlob::Ref(key_blob); + + let (begin_result, _) = self + .upgrade_keyblob_if_required_with(db, key_id_guard, key_blob, |blob| { + let _wp = + wd::watch("KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::begin"); + self.km_dev + .begin(purpose, blob, operation_parameters, auth_token) + }) + .context(err!("Failed to begin operation."))?; + let operation: Strong = begin_result + .operation + .ok_or_else(Error::sys) + .context(err!("Operation missing"))?; + let _wp = wd::watch("KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::finish"); + operation + .finish(Some(input), None, None, None, None) + .context(err!("Failed to finish operation.")) + } +} + +static mut KM_WRAPPER_STRONGBOX: OnceLock = OnceLock::new(); + +static mut KM_WRAPPER_TEE: OnceLock = OnceLock::new(); + +struct KeyMintWrapper { + km: KeyMintTa, + security_level: SecurityLevel, +} + +unsafe impl Sync for KeyMintWrapper {} + +impl KeyMintWrapper { + fn new(security_level: SecurityLevel) -> Result { + Ok(KeyMintWrapper { + km: init_keymint_ta(security_level)?, + security_level: security_level.clone(), + }) + } + + fn begin( + &self, + purpose: KeyPurpose, + key_blob: &[u8], + params: &[KeyParameter], + auth_token: Option<&HardwareAuthToken>, + ) -> Result { + let calling_context = CallingContext::get(); + self.km + .begin( + &calling_context, + purpose, + key_blob, + params, + auth_token, + None, + ) + .map_err(|e| anyhow!(err!("KeyMintWrapper::begin failed: {:?}", e))) + } +} + +pub fn get_keymint_security_level( + security_level: SecurityLevel, +) -> Result { + match security_level { + SecurityLevel::TRUSTED_ENVIRONMENT => { + Ok(kmr_wire::keymint::SecurityLevel::TrustedEnvironment) + } + SecurityLevel::STRONGBOX => Ok(kmr_wire::keymint::SecurityLevel::Strongbox), + _ => Err(anyhow::anyhow!(err!("Unknown security level"))), + } +} + +pub fn get_keymaster_security_level( + security_level: kmr_wire::keymint::SecurityLevel, +) -> Result { + match security_level { + kmr_wire::keymint::SecurityLevel::TrustedEnvironment => { + Ok(SecurityLevel::TRUSTED_ENVIRONMENT) + } + kmr_wire::keymint::SecurityLevel::Strongbox => Ok(SecurityLevel::STRONGBOX), + _ => Err(anyhow::anyhow!(err!("Unknown security level"))), + } +} + +fn init_keymint_ta(security_level: SecurityLevel) -> Result { + let security_level = get_keymint_security_level(security_level)?; + + let hw_info = HardwareInfo { + version_number: 2, + security_level, + impl_name: "Qualcomm QTEE KeyMint 2", + author_name: "Qualcomm Technologies", + unique_id: "Qualcomm QTEE KeyMint 2", + }; + + let rpc_sign_algo = CsrSigningAlgorithm::EdDSA; + let rpc_info_v3 = RpcInfoV3 { + author_name: "Qualcomm Technologies", + unique_id: "Qualcomm QTEE KeyMint 2", + fused: false, + supported_num_of_keys_in_csr: MINIMUM_SUPPORTED_KEYS_IN_CSR, + }; + + let mut rng = BoringRng; + + let sdd_mgr: Option> = + match sdd::HostSddManager::new(&mut rng) { + Ok(v) => Some(Box::new(v)), + Err(e) => { + error!("Failed to initialize secure deletion data manager: {:?}", e); + None + } + }; + + let clock = clock::StdClock; + let rsa = BoringRsa::default(); + let ec = BoringEc::default(); + let hkdf: Box = Box::new(BoringHmac); + let imp = kmr_common::crypto::Implementation { + rng: Box::new(rng), + clock: Some(Box::new(clock)), + compare: Box::new(kmr_crypto_boring::eq::BoringEq), + aes: Box::new(kmr_crypto_boring::aes::BoringAes), + des: Box::new(kmr_crypto_boring::des::BoringDes), + hmac: Box::new(BoringHmac), + rsa: Box::new(rsa), + ec: Box::new(ec), + ckdf: Box::new(kmr_crypto_boring::aes_cmac::BoringAesCmac), + hkdf, + sha256: Box::new(kmr_crypto_boring::sha256::BoringSha256), + }; + + let keys: Box = Box::new(soft::Keys); + let rpc: Box = Box::new(soft::RpcArtifacts::new( + soft::Derive::default(), + rpc_sign_algo, + )); + + let dev = kmr_ta::device::Implementation { + keys, + // Cuttlefish has `remote_provisioning.tee.rkp_only=1` so don't support batch signing + // of keys. This can be reinstated with: + // ``` + // sign_info: Some(kmr_ta_nonsecure::attest::CertSignInfo::new()), + // ``` + sign_info: Some(Box::new(crate::keymint::attest::CertSignInfo::new())), + // HAL populates attestation IDs from properties. + attest_ids: None, + sdd_mgr, + // `BOOTLOADER_ONLY` keys not supported. + bootloader: Box::new(kmr_ta::device::BootloaderDone), + // `STORAGE_KEY` keys not supported. + sk_wrapper: None, + // `TRUSTED_USER_PRESENCE_REQUIRED` keys not supported + tup: Box::new(kmr_ta::device::TrustedPresenceUnsupported), + // No support for converting previous implementation's keyblobs. + legacy_key: None, + rpc, + }; + + Ok(KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev)) +} + +fn get_keymint_device( + security_level: SecurityLevel, +) -> Result<(KeyMintWrapper, KeyMintHardwareInfo)> { + match security_level { + SecurityLevel::STRONGBOX => { + let strongbox = unsafe { + *KM_WRAPPER_STRONGBOX.get_or_init(|| { + KeyMintWrapper::new(SecurityLevel::STRONGBOX) + .expect(err!("Failed to init strongbox wrapper")) + }) + }; + let info = strongbox + .km + .get_hardware_info() + .expect(err!("Failed to get hardware info")); + Ok((strongbox, info)) + } + SecurityLevel::TRUSTED_ENVIRONMENT => { + let tee = unsafe { + *KM_WRAPPER_TEE.get_or_init(|| { + KeyMintWrapper::new(SecurityLevel::TRUSTED_ENVIRONMENT) + .expect(err!("Failed to init tee wrapper")) + }) + }; + let info = tee + .km + .get_hardware_info() + .expect(err!("Failed to get hardware info")); + Ok((tee, info)) + } + SecurityLevel::SOFTWARE => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context(err!("Software KeyMint not supported")), + _ => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context(err!("Unknown security level")), + } +} diff --git a/libs/rust/src/keymaster/mod.rs b/libs/rust/src/keymaster/mod.rs index b8b00b1..3e6407a 100644 --- a/libs/rust/src/keymaster/mod.rs +++ b/libs/rust/src/keymaster/mod.rs @@ -1,2 +1,13 @@ +pub mod attestation_key_utils; +pub mod boot_key; +pub mod crypto; +pub mod database; pub mod db; -pub mod database; \ No newline at end of file +pub mod enforcements; +pub mod error; +pub mod key_parameter; +pub mod keymint_device; +pub mod permission; +pub mod security_level; +pub mod super_key; +pub mod utils; diff --git a/libs/rust/src/keymaster/permission.rs b/libs/rust/src/keymaster/permission.rs new file mode 100644 index 0000000..258fc50 --- /dev/null +++ b/libs/rust/src/keymaster/permission.rs @@ -0,0 +1,437 @@ +use crate::android::system::keystore2::KeyPermission::KeyPermission; + +/// This macro implements an enum with values mapped to SELinux permission names. +/// The example below implements `enum MyPermission with public visibility: +/// * From and Into are implemented. Where the implementation of From maps +/// any variant not specified to the default `None` with value `0`. +/// * `MyPermission` implements ClassPermission. +/// * An implicit default values `MyPermission::None` is created with a numeric representation +/// of `0` and a string representation of `"none"`. +/// * Specifying a value is optional. If the value is omitted it is set to the value of the +/// previous variant left shifted by 1. +/// +/// ## Example +/// ``` +/// implement_class!( +/// /// MyPermission documentation. +/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// #[selinux(class_name = my_class)] +/// pub enum MyPermission { +/// #[selinux(name = foo)] +/// Foo = 1, +/// #[selinux(name = bar)] +/// Bar = 2, +/// #[selinux(name = snafu)] +/// Snafu, // Implicit value: MyPermission::Bar << 1 -> 4 +/// } +/// assert_eq!(MyPermission::Foo.name(), &"foo"); +/// assert_eq!(MyPermission::Foo.class_name(), &"my_class"); +/// assert_eq!(MyPermission::Snafu as i32, 4); +/// ); +/// ``` +#[macro_export] +macro_rules! implement_class { + // First rule: Public interface. + ( + $(#[$($enum_meta:tt)+])* + $enum_vis:vis enum $enum_name:ident $body:tt + ) => { + implement_class! { + @extract_class + [] + [$(#[$($enum_meta)+])*] + $enum_vis enum $enum_name $body + } + }; + + // The next two rules extract the #[selinux(class_name = )] meta field from + // the types meta list. + // This first rule finds the field and terminates the recursion through the meta fields. + ( + @extract_class + [$(#[$mout:meta])*] + [ + #[selinux(class_name = $class_name:ident)] + $(#[$($mtail:tt)+])* + ] + $enum_vis:vis enum $enum_name:ident { + $( + $(#[$($emeta:tt)+])* + $vname:ident$( = $vval:expr)? + ),* $(,)? + } + ) => { + implement_class!{ + @extract_perm_name + $class_name + $(#[$mout])* + $(#[$($mtail)+])* + $enum_vis enum $enum_name { + 1; + [] + [$( + [] [$(#[$($emeta)+])*] + $vname$( = $vval)?, + )*] + } + } + }; + + // The second rule iterates through the type global meta fields. + ( + @extract_class + [$(#[$mout:meta])*] + [ + #[$front:meta] + $(#[$($mtail:tt)+])* + ] + $enum_vis:vis enum $enum_name:ident $body:tt + ) => { + implement_class!{ + @extract_class + [ + $(#[$mout])* + #[$front] + ] + [$(#[$($mtail)+])*] + $enum_vis enum $enum_name $body + } + }; + + // The next four rules implement two nested recursions. The outer iterates through + // the enum variants and the inner iterates through the meta fields of each variant. + // The first two rules find the #[selinux(name = )] stanza, terminate the inner + // recursion and descend a level in the outer recursion. + // The first rule matches variants with explicit initializer $vval. And updates the next + // value to ($vval << 1). + ( + @extract_perm_name + $class_name:ident + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $next_val:expr; + [$($out:tt)*] + [ + [$(#[$mout:meta])*] + [ + #[selinux(name = $selinux_name:ident)] + $(#[$($mtail:tt)+])* + ] + $vname:ident = $vval:expr, + $($tail:tt)* + ] + } + ) => { + implement_class!{ + @extract_perm_name + $class_name + $(#[$enum_meta])* + $enum_vis enum $enum_name { + ($vval << 1); + [ + $($out)* + $(#[$mout])* + $(#[$($mtail)+])* + $selinux_name $vname = $vval, + ] + [$($tail)*] + } + } + }; + + // The second rule differs form the previous in that there is no explicit initializer. + // Instead $next_val is used as initializer and the next value is set to (&next_val << 1). + ( + @extract_perm_name + $class_name:ident + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $next_val:expr; + [$($out:tt)*] + [ + [$(#[$mout:meta])*] + [ + #[selinux(name = $selinux_name:ident)] + $(#[$($mtail:tt)+])* + ] + $vname:ident, + $($tail:tt)* + ] + } + ) => { + implement_class!{ + @extract_perm_name + $class_name + $(#[$enum_meta])* + $enum_vis enum $enum_name { + ($next_val << 1); + [ + $($out)* + $(#[$mout])* + $(#[$($mtail)+])* + $selinux_name $vname = $next_val, + ] + [$($tail)*] + } + } + }; + + // The third rule descends a step in the inner recursion. + ( + @extract_perm_name + $class_name:ident + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $next_val:expr; + [$($out:tt)*] + [ + [$(#[$mout:meta])*] + [ + #[$front:meta] + $(#[$($mtail:tt)+])* + ] + $vname:ident$( = $vval:expr)?, + $($tail:tt)* + ] + } + ) => { + implement_class!{ + @extract_perm_name + $class_name + $(#[$enum_meta])* + $enum_vis enum $enum_name { + $next_val; + [$($out)*] + [ + [ + $(#[$mout])* + #[$front] + ] + [$(#[$($mtail)+])*] + $vname$( = $vval)?, + $($tail)* + ] + } + } + }; + + // The fourth rule terminates the outer recursion and transitions to the + // implementation phase @spill. + ( + @extract_perm_name + $class_name:ident + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $next_val:expr; + [$($out:tt)*] + [] + } + ) => { + implement_class!{ + @spill + $class_name + $(#[$enum_meta])* + $enum_vis enum $enum_name { + $($out)* + } + } + }; + + ( + @spill + $class_name:ident + $(#[$enum_meta:meta])* + $enum_vis:vis enum $enum_name:ident { + $( + $(#[$emeta:meta])* + $selinux_name:ident $vname:ident = $vval:expr, + )* + } + ) => { + $(#[$enum_meta])* + $enum_vis enum $enum_name { + /// The default variant of the enum. + None = 0, + $( + $(#[$emeta])* + $vname = $vval, + )* + } + + impl From for $enum_name { + #[allow(non_upper_case_globals)] + fn from (p: i32) -> Self { + // Creating constants forces the compiler to evaluate the value expressions + // so that they can be used in the match statement below. + $(const $vname: i32 = $vval;)* + match p { + 0 => Self::None, + $($vname => Self::$vname,)* + _ => Self::None, + } + } + } + + impl From<$enum_name> for i32 { + fn from(p: $enum_name) -> i32 { + p as i32 + } + } + }; +} + +implement_class!( + /// KeyPerm provides a convenient abstraction from the SELinux class `keystore2_key`. + /// At the same time it maps `KeyPermissions` from the Keystore 2.0 AIDL Grant interface to + /// the SELinux permissions. + #[repr(i32)] + #[selinux(class_name = keystore2_key)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum KeyPerm { + /// Checked when convert_storage_key_to_ephemeral is called. + #[selinux(name = convert_storage_key_to_ephemeral)] + ConvertStorageKeyToEphemeral = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0, + /// Checked when the caller tries do delete a key. + #[selinux(name = delete)] + Delete = KeyPermission::DELETE.0, + /// Checked when the caller tries to use a unique id. + #[selinux(name = gen_unique_id)] + GenUniqueId = KeyPermission::GEN_UNIQUE_ID.0, + /// Checked when the caller tries to load a key. + #[selinux(name = get_info)] + GetInfo = KeyPermission::GET_INFO.0, + /// Checked when the caller attempts to grant a key to another uid. + /// Also used for gating key migration attempts. + #[selinux(name = grant)] + Grant = KeyPermission::GRANT.0, + /// Checked when the caller attempts to use Domain::BLOB. + #[selinux(name = manage_blob)] + ManageBlob = KeyPermission::MANAGE_BLOB.0, + /// Checked when the caller tries to create a key which implies rebinding + /// an alias to the new key. + #[selinux(name = rebind)] + Rebind = KeyPermission::REBIND.0, + /// Checked when the caller attempts to create a forced operation. + #[selinux(name = req_forced_op)] + ReqForcedOp = KeyPermission::REQ_FORCED_OP.0, + /// Checked when the caller attempts to update public key artifacts. + #[selinux(name = update)] + Update = KeyPermission::UPDATE.0, + /// Checked when the caller attempts to use a private or public key. + #[selinux(name = use)] + Use = KeyPermission::USE.0, + /// Does nothing, and is not checked. For use of device identifiers, + /// the caller must hold the READ_PRIVILEGED_PHONE_STATE Android + /// permission. + #[selinux(name = use_dev_id)] + UseDevId = KeyPermission::USE_DEV_ID.0, + } +); + +/// Represents a set of `KeyPerm` permissions. +/// `IntoIterator` is implemented for this struct allowing the iteration through all the +/// permissions in the set. +/// It also implements a function `includes(self, other)` that checks if the permissions +/// in `other` are included in `self`. +/// +/// KeyPermSet can be created with the macro `key_perm_set![]`. +/// +/// ## Example +/// ``` +/// let perms1 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob, KeyPerm::Grant]; +/// let perms2 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob]; +/// +/// assert!(perms1.includes(perms2)) +/// assert!(!perms2.includes(perms1)) +/// +/// let i = perms1.into_iter(); +/// // iteration in ascending order of the permission's numeric representation. +/// assert_eq(Some(KeyPerm::ManageBlob), i.next()); +/// assert_eq(Some(KeyPerm::Grant), i.next()); +/// assert_eq(Some(KeyPerm::Use), i.next()); +/// assert_eq(None, i.next()); +/// ``` +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct KeyPermSet(pub i32); + +mod perm { + use super::*; + + pub struct IntoIter { + vec: KeyPermSet, + pos: u8, + } + + impl IntoIter { + pub fn new(v: KeyPermSet) -> Self { + Self { vec: v, pos: 0 } + } + } + + impl std::iter::Iterator for IntoIter { + type Item = KeyPerm; + + fn next(&mut self) -> Option { + loop { + if self.pos == 32 { + return None; + } + let p = self.vec.0 & (1 << self.pos); + self.pos += 1; + if p != 0 { + return Some(KeyPerm::from(p)); + } + } + } + } +} + +impl From for KeyPermSet { + fn from(p: KeyPerm) -> Self { + Self(p as i32) + } +} + +/// allow conversion from the AIDL wire type i32 to a permission set. +impl From for KeyPermSet { + fn from(p: i32) -> Self { + Self(p) + } +} + +impl From for i32 { + fn from(p: KeyPermSet) -> i32 { + p.0 + } +} + +impl KeyPermSet { + /// Returns true iff this permission set has all of the permissions that are in `other`. + pub fn includes>(&self, other: T) -> bool { + let o: KeyPermSet = other.into(); + (self.0 & o.0) == o.0 + } +} + +/// This macro can be used to create a `KeyPermSet` from a list of `KeyPerm` values. +/// +/// ## Example +/// ``` +/// let v = key_perm_set![Perm::delete(), Perm::manage_blob()]; +/// ``` +#[macro_export] +macro_rules! key_perm_set { + () => { KeyPermSet(0) }; + ($head:expr $(, $tail:expr)* $(,)?) => { + KeyPermSet($head as i32 $(| $tail as i32)*) + }; +} + +impl IntoIterator for KeyPermSet { + type Item = KeyPerm; + type IntoIter = perm::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter::new(self) + } +} diff --git a/libs/rust/src/keymaster/security_level.rs b/libs/rust/src/keymaster/security_level.rs new file mode 100644 index 0000000..8ddc49e --- /dev/null +++ b/libs/rust/src/keymaster/security_level.rs @@ -0,0 +1,456 @@ +use std::time::SystemTime; + +use crate::{ + android::{ + hardware::security::keymint::{ + Algorithm::Algorithm, AttestationKey::AttestationKey, ErrorCode::ErrorCode, + KeyCreationResult::KeyCreationResult, KeyParameter::KeyParameter, + KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, + }, + system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, + ResponseCode::ResponseCode, + }, + }, + err, + global::{DB, SUPER_KEY, UNDEFINED_NOT_AFTER}, + keymaster::{ + attestation_key_utils::{get_attest_key_info, AttestationKeyInfo}, + db::{ + BlobInfo, BlobMetaEntry, CertificateInfo, DateTime, DateTimeError, KeyIdGuard, + KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, Uuid, + }, + error::KsError, + keymint_device::KeyMintDevice, + super_key::{KeyBlob, SuperKeyManager}, + utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, + }, +}; + +use crate::keymaster::key_parameter::KeyParameter as KsKeyParam; +use crate::keymaster::key_parameter::KeyParameterValue as KsKeyParamValue; + +use crate::watchdog as wd; + +use anyhow::{anyhow, Context, Result}; +use kmr_ta::HardwareInfo; +use rsbinder::thread_state::CallingContext; + +pub struct KeystoreSecurityLevel { + security_level: SecurityLevel, + hw_info: HardwareInfo, + km_uuid: Uuid, + keymint: KeyMintDevice, +} + +impl KeystoreSecurityLevel { + pub fn new( + security_level: SecurityLevel, + hw_info: HardwareInfo, + km_uuid: Uuid, + keymint: KeyMintDevice, + ) -> Self { + KeystoreSecurityLevel { + security_level, + hw_info, + km_uuid, + keymint, + } + } + + fn watch_millis(&self, id: &'static str, millis: u64) -> Option { + let sec_level = self.security_level; + wd::watch_millis_with(id, millis, sec_level) + } + + fn watch(&self, id: &'static str) -> Option { + let sec_level = self.security_level; + wd::watch_millis_with(id, wd::DEFAULT_TIMEOUT_MS, sec_level) + } + + fn store_upgraded_keyblob( + key_id_guard: KeyIdGuard, + km_uuid: Option, + key_blob: &KeyBlob, + upgraded_blob: &[u8], + ) -> Result<()> { + let (upgraded_blob_to_be_stored, new_blob_metadata) = + SuperKeyManager::reencrypt_if_required(key_blob, upgraded_blob) + .context(err!("Failed to handle super encryption."))?; + + let mut new_blob_metadata = new_blob_metadata.unwrap_or_default(); + if let Some(uuid) = km_uuid { + new_blob_metadata.add(BlobMetaEntry::KmUuid(uuid)); + } + + DB.with(|db| { + let mut db = db.borrow_mut(); + db.set_blob( + &key_id_guard, + SubComponentType::KEY_BLOB, + Some(&upgraded_blob_to_be_stored), + Some(&new_blob_metadata), + ) + }) + .context(err!("Failed to insert upgraded blob into the database.")) + } + + fn add_required_parameters( + &self, + uid: u32, + params: &[KeyParameter], + _key: &KeyDescriptor, + ) -> Result> { + let mut result = params.to_vec(); + + // Prevent callers from specifying the CREATION_DATETIME tag. + if params.iter().any(|kp| kp.tag == Tag::CREATION_DATETIME) { + return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)).context(err!( + "KeystoreSecurityLevel::add_required_parameters: \ + Specifying Tag::CREATION_DATETIME is not allowed." + )); + } + + // Use this variable to refer to notion of "now". This eliminates discrepancies from + // quering the clock multiple times. + let creation_datetime = SystemTime::now(); + + // Add CREATION_DATETIME only if the backend version Keymint V1 (100) or newer. + if self.hw_info.version_number >= 100 { + result.push(KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KeyParameterValue::DateTime( + creation_datetime + .duration_since(SystemTime::UNIX_EPOCH) + .context(err!( + "KeystoreSecurityLevel::add_required_parameters: \ + Failed to get epoch time." + ))? + .as_millis() + .try_into() + .context(err!( + "KeystoreSecurityLevel::add_required_parameters: \ + Failed to convert epoch time." + ))?, + ), + }); + } + + // If there is an attestation challenge we need to get an application id. + if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) { + match crate::plat::utils::get_aaid(uid) { + Ok(aaid_ok) => { + result.push(KeyParameter { + tag: Tag::ATTESTATION_APPLICATION_ID, + value: KeyParameterValue::Blob(aaid_ok.into_bytes()), + }); + } + Err(e) => return Err(anyhow!(e)).context(err!("Attestation ID retrieval error.")), + } + } + + // if params.iter().any(|kp| kp.tag == Tag::INCLUDE_UNIQUE_ID) { + // if check_key_permission(KeyPerm::GenUniqueId, key, &None).is_err() + // && check_unique_id_attestation_permissions().is_err() + // { + // return Err(Error::perm()).context(err!( + // "Caller does not have the permission to generate a unique ID" + // )); + // } + // } + + // If the caller requests any device identifier attestation tag, check that they hold the + // correct Android permission. + // if params.iter().any(|kp| is_device_id_attestation_tag(kp.tag)) { + // check_device_attestation_permissions().context(err!( + // "Caller does not have the permission to attest device identifiers." + // ))?; + // } + + // If we are generating/importing an asymmetric key, we need to make sure + // that NOT_BEFORE and NOT_AFTER are present. + match params.iter().find(|kp| kp.tag == Tag::ALGORITHM) { + Some(KeyParameter { + tag: _, + value: KeyParameterValue::Algorithm(Algorithm::RSA), + }) + | Some(KeyParameter { + tag: _, + value: KeyParameterValue::Algorithm(Algorithm::EC), + }) => { + if !params + .iter() + .any(|kp| kp.tag == Tag::CERTIFICATE_NOT_BEFORE) + { + result.push(KeyParameter { + tag: Tag::CERTIFICATE_NOT_BEFORE, + value: KeyParameterValue::DateTime(0), + }) + } + if !params.iter().any(|kp| kp.tag == Tag::CERTIFICATE_NOT_AFTER) { + result.push(KeyParameter { + tag: Tag::CERTIFICATE_NOT_AFTER, + value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER), + }) + } + } + _ => {} + } + Ok(result) + } + + fn upgrade_keyblob_if_required_with( + &self, + mut key_id_guard: Option, + key_blob: &KeyBlob, + km_uuid: Option, + params: &[KeyParameter], + f: F, + ) -> Result<(T, Option>)> + where + F: Fn(&[u8]) -> Result, + { + let (v, upgraded_blob) = crate::keymaster::utils::upgrade_keyblob_if_required_with( + &*self.keymint, + self.hw_info.version_number, + key_blob, + params, + f, + |upgraded_blob| { + if key_id_guard.is_some() { + // Unwrap cannot panic, because the is_some was true. + let kid = key_id_guard.take().unwrap(); + Self::store_upgraded_keyblob(kid, km_uuid, key_blob, upgraded_blob) + .context(err!("store_upgraded_keyblob failed")) + } else { + Ok(()) + } + }, + ) + .context(err!( + "upgrade_keyblob_if_required_with(key_id={:?})", + key_id_guard + ))?; + + // If no upgrade was needed, use the opportunity to reencrypt the blob if required + // and if the a key_id_guard is held. Note: key_id_guard can only be Some if no + // upgrade was performed above and if one was given in the first place. + if key_blob.force_reencrypt() { + if let Some(kid) = key_id_guard { + Self::store_upgraded_keyblob(kid, km_uuid, key_blob, key_blob) + .context(err!("store_upgraded_keyblob failed in forced reencrypt"))?; + } + } + Ok((v, upgraded_blob)) + } + + fn store_new_key( + &self, + key: KeyDescriptor, + creation_result: KeyCreationResult, + user_id: u32, + flags: Option, + ) -> Result { + let KeyCreationResult { + keyBlob: key_blob, + keyCharacteristics: key_characteristics, + certificateChain: mut certificate_chain, + } = creation_result; + + // Unify the possible contents of the certificate chain. The first entry in the `Vec` is + // always the leaf certificate (if present), but the rest of the chain may be present as + // either: + // - `certificate_chain[1..n]`: each entry holds a single certificate, as returned by + // KeyMint, or + // - `certificate[1`]: a single `Certificate` from RKP that actually (and confusingly) + // holds the DER-encoded certs of the chain concatenated together. + let mut cert_info: CertificateInfo = CertificateInfo::new( + // Leaf is always a single cert in the first entry, if present. + match certificate_chain.len() { + 0 => None, + _ => Some(certificate_chain.remove(0).encodedCertificate), + }, + // Remainder may be either `[1..n]` individual certs, or just `[1]` holding a + // concatenated chain. Convert the former to the latter. + match certificate_chain.len() { + 0 => None, + _ => Some( + certificate_chain + .iter() + .flat_map(|c| c.encodedCertificate.iter()) + .copied() + .collect(), + ), + }, + ); + + let mut key_parameters = key_characteristics_to_internal(key_characteristics); + + key_parameters.push(KsKeyParam::new( + KsKeyParamValue::UserID(user_id as i32), + SecurityLevel::SOFTWARE, + )); + + let creation_date = DateTime::now().context(err!("Trying to make creation time."))?; + + let key = match key.domain { + Domain::BLOB => KeyDescriptor { + domain: Domain::BLOB, + blob: Some(key_blob.to_vec()), + ..Default::default() + }, + _ => DB + .with::<_, Result>(|db| { + let mut db = db.borrow_mut(); + + let (key_blob, mut blob_metadata) = SUPER_KEY + .read() + .unwrap() + .handle_super_encryption_on_key_init( + &mut db, + &(key.domain), + &key_parameters, + flags, + user_id, + &key_blob, + ) + .context(err!("Failed to handle super encryption."))?; + + let mut key_metadata = KeyMetaData::new(); + key_metadata.add(KeyMetaEntry::CreationDate(creation_date)); + blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); + + let key_id = db + .store_new_key( + &key, + KeyType::Client, + &key_parameters, + &BlobInfo::new(&key_blob, &blob_metadata), + &cert_info, + &key_metadata, + &self.km_uuid, + ) + .context(err!())?; + Ok(KeyDescriptor { + domain: Domain::KEY_ID, + nspace: key_id.id(), + ..Default::default() + }) + }) + .context(err!())?, + }; + + Ok(KeyMetadata { + key, + keySecurityLevel: self.security_level, + certificate: cert_info.take_cert(), + certificateChain: cert_info.take_cert_chain(), + authorizations: key_parameters_to_authorizations(key_parameters), + modificationTimeMs: creation_date.to_millis_epoch(), + }) + } + + #[allow(unused_variables)] + pub fn generate_key( + &self, + key: &KeyDescriptor, + attest_key_descriptor: Option<&KeyDescriptor>, + params: &[KeyParameter], + flags: i32, + _entropy: &[u8], + ) -> Result { + if key.domain != Domain::BLOB && key.alias.is_none() { + return Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("Alias must be provided for non-BLOB domains")); + } + let calling_context = CallingContext::default(); + let calling_uid = calling_context.uid; + + let key = match key.domain { + Domain::APP => KeyDescriptor { + domain: key.domain, + nspace: calling_uid as i64, + alias: key.alias.clone(), + blob: None, + }, + _ => key.clone(), + }; + + // TODO: check perms + + let attestation_key_info = match (key.domain, attest_key_descriptor) { + (Domain::BLOB, _) => None, + _ => DB + .with(|db| { + get_attest_key_info( + &key, + calling_uid, + attest_key_descriptor, + params, + &mut db.borrow_mut(), + ) + }) + .context(err!("Trying to get an attestation key"))?, + }; + + let params = self + .add_required_parameters(calling_uid, params, &key) + .context(err!("Trying to get aaid."))?; + + let creation_result = match attestation_key_info { + Some(AttestationKeyInfo::UserGenerated { + key_id_guard, + blob, + blob_metadata, + issuer_subject, + }) => self + .upgrade_keyblob_if_required_with( + Some(key_id_guard), + &KeyBlob::Ref(&blob), + blob_metadata.km_uuid().copied(), + ¶ms, + |blob| { + let attest_key = Some(AttestationKey { + keyBlob: blob.to_vec(), + attestKeyParams: vec![], + issuerSubjectName: issuer_subject.clone(), + }); + let _wp = self.watch_millis( + concat!( + "KeystoreSecurityLevel::generate_key (UserGenerated): ", + "calling IKeyMintDevice::generate_key" + ), + 5000, // Generate can take a little longer. + ); + self.keymint.generateKey(¶ms, attest_key.as_ref()) + }, + ) + .context(err!( + "While generating with a user-generated \ + attestation key, params: {:?}.", + log_params(¶ms) + )) + .map(|(result, _)| result), + None => { + let _wp = self.watch_millis( + concat!( + "KeystoreSecurityLevel::generate_key (No attestation key): ", + "calling IKeyMintDevice::generate_key", + ), + 5000, // Generate can take a little longer. + ); + self.keymint.generateKey(¶ms, None) + } + .context(err!( + "While generating without a provided \ + attestation key and params: {:?}.", + log_params(¶ms) + )), + _ => unreachable!(), // Other branches of get_attest_key_info are not possible here. + }?; + + let user_id = crate::plat::utils::uid_to_android_user(calling_uid); + self.store_new_key(key, creation_result, user_id, Some(flags)) + .context(err!()) + } +} diff --git a/libs/rust/src/keymaster/super_key.rs b/libs/rust/src/keymaster/super_key.rs new file mode 100644 index 0000000..a7a089c --- /dev/null +++ b/libs/rust/src/keymaster/super_key.rs @@ -0,0 +1,1270 @@ +// 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. + +use crate::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, HardwareAuthToken::HardwareAuthToken, + HardwareAuthenticatorType::HardwareAuthenticatorType, KeyFormat::KeyFormat, + KeyParameter::KeyParameter as KmKeyParameter, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, + SecurityLevel::SecurityLevel, +}; +use crate::android::system::keystore2::{Domain::Domain, KeyDescriptor::KeyDescriptor}; +use crate::keymaster::crypto::ECDHPrivateKey; +use crate::keymaster::enforcements::Enforcements; +use crate::watchdog as wd; +use crate::{ + android::system::keystore2::ResponseCode::ResponseCode, + err, + global::AID_KEYSTORE, + keymaster::{ + boot_key::{get_level_zero_key, BootLevelKeyCache}, + db::{ + BlobMetaData, BlobMetaEntry, EncryptedBy, KeyEntry, KeyEntryLoadBits, KeyIdGuard, + KeyMetaData, KeyMetaEntry, KeyType, KeymasterDb, + }, + error::KsError as Error, + key_parameter::{KeyParameter, KeyParameterValue}, + keymint_device::KeyMintDevice, + }, + plat::property_watcher::PropertyWatcher, +}; +use anyhow::{anyhow, Context, Result}; +use kmr_common::crypto::AES_256_KEY_LENGTH; +use kmr_crypto_boring::km::*; +use kmr_crypto_boring::zvec::ZVec; +use std::{ + collections::HashMap, + sync::Arc, + sync::{Mutex, RwLock, Weak}, +}; +use std::{convert::TryFrom, ops::Deref}; + +const MAX_MAX_BOOT_LEVEL: usize = 1_000_000_000; +/// Allow up to 15 seconds between the user unlocking using a biometric, and the auth +/// token being used to unlock in [`SuperKeyManager::try_unlock_user_with_biometric`]. +/// This seems short enough for security purposes, while long enough that even the +/// very slowest device will present the auth token in time. +const BIOMETRIC_AUTH_TIMEOUT_S: i32 = 15; // seconds + +type UserId = u32; + +/// Encryption algorithm used by a particular type of superencryption key +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SuperEncryptionAlgorithm { + /// Symmetric encryption with AES-256-GCM + Aes256Gcm, + /// Public-key encryption with ECDH P-521 + EcdhP521, +} + +/// A particular user may have several superencryption keys in the database, each for a +/// different purpose, distinguished by alias. Each is associated with a static +/// constant of this type. +pub struct SuperKeyType<'a> { + /// Alias used to look up the key in the `persistent.keyentry` table. + pub alias: &'a str, + /// Encryption algorithm + pub algorithm: SuperEncryptionAlgorithm, + /// What to call this key in log messages. Not used for anything else. + pub name: &'a str, +} + +/// The user's AfterFirstUnlock super key. This super key is loaded into memory when the user first +/// unlocks the device, and it remains in memory until the device reboots. This is used to encrypt +/// keys that require user authentication but not an unlocked device. +pub const USER_AFTER_FIRST_UNLOCK_SUPER_KEY: SuperKeyType = SuperKeyType { + alias: "USER_SUPER_KEY", + algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "AfterFirstUnlock super key", +}; + +/// The user's UnlockedDeviceRequired symmetric super key. This super key is loaded into memory each +/// time the user unlocks the device, and it is cleared from memory each time the user locks the +/// device. This is used to encrypt keys that use the UnlockedDeviceRequired key parameter. +pub const USER_UNLOCKED_DEVICE_REQUIRED_SYMMETRIC_SUPER_KEY: SuperKeyType = SuperKeyType { + alias: "USER_SCREEN_LOCK_BOUND_KEY", + algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "UnlockedDeviceRequired symmetric super key", +}; + +/// The user's UnlockedDeviceRequired asymmetric super key. This is used to allow, while the device +/// is locked, the creation of keys that use the UnlockedDeviceRequired key parameter. The private +/// part of this key is loaded and cleared when the symmetric key is loaded and cleared. +pub const USER_UNLOCKED_DEVICE_REQUIRED_P521_SUPER_KEY: SuperKeyType = SuperKeyType { + alias: "USER_SCREEN_LOCK_BOUND_P521_KEY", + algorithm: SuperEncryptionAlgorithm::EcdhP521, + name: "UnlockedDeviceRequired asymmetric super key", +}; + +/// Superencryption to apply to a new key. +#[derive(Debug, Clone, Copy)] +pub enum SuperEncryptionType { + /// Do not superencrypt this key. + None, + /// Superencrypt with the AfterFirstUnlock super key. + AfterFirstUnlock, + /// Superencrypt with an UnlockedDeviceRequired super key. + UnlockedDeviceRequired, + /// Superencrypt with a key based on the desired boot level + BootLevel(i32), +} + +#[derive(Debug, Clone, Copy)] +pub enum SuperKeyIdentifier { + /// id of the super key in the database. + DatabaseId(i64), + /// Boot level of the encrypting boot level key + BootLevel(i32), +} + +impl SuperKeyIdentifier { + fn from_metadata(metadata: &BlobMetaData) -> Option { + if let Some(EncryptedBy::KeyId(key_id)) = metadata.encrypted_by() { + Some(SuperKeyIdentifier::DatabaseId(*key_id)) + } else { + metadata + .max_boot_level() + .map(|boot_level| SuperKeyIdentifier::BootLevel(*boot_level)) + } + } + + fn add_to_metadata(&self, metadata: &mut BlobMetaData) { + match self { + SuperKeyIdentifier::DatabaseId(id) => { + metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(*id))); + } + SuperKeyIdentifier::BootLevel(level) => { + metadata.add(BlobMetaEntry::MaxBootLevel(*level)); + } + } + } +} + +pub struct SuperKey { + algorithm: SuperEncryptionAlgorithm, + key: ZVec, + /// Identifier of the encrypting key, used to write an encrypted blob + /// back to the database after re-encryption eg on a key update. + id: SuperKeyIdentifier, + /// ECDH is more expensive than AES. So on ECDH private keys we set the + /// reencrypt_with field to point at the corresponding AES key, and the + /// keys will be re-encrypted with AES on first use. + reencrypt_with: Option>, +} + +trait AesGcm { + fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result; + fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec, Vec, Vec)>; +} + +impl AesGcm for SuperKey { + fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result { + if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm { + aes_gcm_decrypt(data, iv, tag, &self.key).context(err!("Decryption failed.")) + } else { + Err(Error::sys()).context(err!("Key is not an AES key.")) + } + } + + fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec, Vec, Vec)> { + if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm { + aes_gcm_encrypt(plaintext, &self.key).context(err!("Encryption failed.")) + } else { + Err(Error::sys()).context(err!("Key is not an AES key.")) + } + } +} + +/// A SuperKey that has been encrypted with an AES-GCM key. For +/// encryption the key is in memory, and for decryption it is in KM. +struct LockedKey { + algorithm: SuperEncryptionAlgorithm, + id: SuperKeyIdentifier, + nonce: Vec, + ciphertext: Vec, // with tag appended +} + +impl LockedKey { + fn new(key: &[u8], to_encrypt: &Arc) -> Result { + let (mut ciphertext, nonce, mut tag) = aes_gcm_encrypt(&to_encrypt.key, key)?; + ciphertext.append(&mut tag); + Ok(LockedKey { + algorithm: to_encrypt.algorithm, + id: to_encrypt.id, + nonce, + ciphertext, + }) + } + + fn decrypt( + &self, + db: &mut KeymasterDb, + km_dev: &super::keymint_device::KeyMintDevice, + key_id_guard: &KeyIdGuard, + key_entry: &KeyEntry, + auth_token: &HardwareAuthToken, + reencrypt_with: Option>, + ) -> Result> { + let key_blob = key_entry + .key_blob_info() + .as_ref() + .map(|(key_blob, _)| KeyBlob::Ref(key_blob)) + .ok_or(Error::Rc(ResponseCode::KEY_NOT_FOUND)) + .context(err!("Missing key blob info."))?; + let key_params = vec![ + KeyParameterValue::Algorithm(Algorithm::AES), + KeyParameterValue::KeySize(256), + KeyParameterValue::BlockMode(BlockMode::GCM), + KeyParameterValue::PaddingMode(PaddingMode::NONE), + KeyParameterValue::Nonce(self.nonce.clone()), + KeyParameterValue::MacLength(128), + ]; + let key_params: Vec = key_params.into_iter().map(|x| x.into()).collect(); + let key = ZVec::try_from(km_dev.use_key_in_one_step( + db, + key_id_guard, + &key_blob, + KeyPurpose::DECRYPT, + &key_params, + Some(auth_token), + &self.ciphertext, + )?)?; + Ok(Arc::new(SuperKey { + algorithm: self.algorithm, + key, + id: self.id, + reencrypt_with, + })) + } +} + +/// A user's UnlockedDeviceRequired super keys, encrypted with a biometric-bound key, and +/// information about that biometric-bound key. +struct BiometricUnlock { + /// List of auth token SIDs that are accepted by the encrypting biometric-bound key. + sids: Vec, + /// Key descriptor of the encrypting biometric-bound key. + key_desc: KeyDescriptor, + /// The UnlockedDeviceRequired super keys, encrypted with a biometric-bound key. + symmetric: LockedKey, + private: LockedKey, +} + +#[derive(Default)] +struct UserSuperKeys { + /// The AfterFirstUnlock super key is used for synthetic password binding of authentication + /// bound keys. There is one key per android user. The key is stored on flash encrypted with a + /// key derived from a secret, that is itself derived from the user's synthetic password. (In + /// most cases, the user's synthetic password can, in turn, only be decrypted using the user's + /// Lock Screen Knowledge Factor or LSKF.) When the user unlocks the device for the first time, + /// this key is unlocked, i.e., decrypted, and stays memory resident until the device reboots. + after_first_unlock: Option>, + /// The UnlockedDeviceRequired symmetric super key works like the AfterFirstUnlock super key + /// with the distinction that it is cleared from memory when the device is locked. + unlocked_device_required_symmetric: Option>, + /// When the device is locked, keys that use the UnlockedDeviceRequired key parameter can still + /// be created, using ECDH public-key encryption. This field holds the decryption private key. + unlocked_device_required_private: Option>, + /// Versions of the above two keys, locked behind a biometric. + biometric_unlock: Option, +} + +#[derive(Default)] +struct SkmState { + user_keys: HashMap, + key_index: HashMap>, + boot_level_key_cache: Option>, +} + +impl SkmState { + fn add_key_to_key_index(&mut self, super_key: &Arc) -> Result<()> { + if let SuperKeyIdentifier::DatabaseId(id) = super_key.id { + self.key_index.insert(id, Arc::downgrade(super_key)); + Ok(()) + } else { + Err(Error::sys()).context(err!("Cannot add key with ID {:?}", super_key.id)) + } + } +} + +#[derive(Default)] +pub struct SuperKeyManager { + data: SkmState, +} + +impl SuperKeyManager { + pub fn set_up_boot_level_cache(skm: &Arc>, db: &mut KeymasterDb) -> Result<()> { + let mut skm_guard = skm.write().unwrap(); + if skm_guard.data.boot_level_key_cache.is_some() { + log::info!("In set_up_boot_level_cache: called for a second time"); + return Ok(()); + } + let level_zero_key = get_level_zero_key(db).context(err!("get_level_zero_key failed"))?; + skm_guard.data.boot_level_key_cache = + Some(Mutex::new(BootLevelKeyCache::new(level_zero_key))); + log::info!("Starting boot level watcher."); + let clone = skm.clone(); + std::thread::spawn(move || { + Self::watch_boot_level(clone) + .unwrap_or_else(|e| log::error!("watch_boot_level failed:\n{:?}", e)); + }); + Ok(()) + } + + /// Watch the `keystore.boot_level` system property, and keep boot level up to date. + /// Blocks waiting for system property changes, so must be run in its own thread. + fn watch_boot_level(skm: Arc>) -> Result<()> { + let w = PropertyWatcher::new("keystore.boot_level") + .context(err!("PropertyWatcher::new failed"))?; + loop { + let level = w + .read_and_parse(|v| v.parse::().map_err(std::convert::Into::into)) + .context(err!("read of property failed"))?; + + // This scope limits the skm_guard life, so we don't hold the skm_guard while + // waiting. + { + let mut skm_guard = skm.write().unwrap(); + let boot_level_key_cache = skm_guard + .data + .boot_level_key_cache + .as_mut() + .ok_or_else(Error::sys) + .context(err!("Boot level cache not initialized"))? + .get_mut() + .unwrap(); + if level < MAX_MAX_BOOT_LEVEL { + log::info!("Read keystore.boot_level value {}", level); + boot_level_key_cache + .advance_boot_level(level) + .context(err!("advance_boot_level failed"))?; + } else { + log::info!( + "keystore.boot_level {} hits maximum {}, finishing.", + level, + MAX_MAX_BOOT_LEVEL + ); + boot_level_key_cache.finish(); + break; + } + } + w.wait(None).context(err!("property wait failed"))?; + } + Ok(()) + } + + pub fn level_accessible(&self, boot_level: i32) -> bool { + self.data.boot_level_key_cache.as_ref().map_or(false, |c| { + c.lock().unwrap().level_accessible(boot_level as usize) + }) + } + + pub fn forget_all_keys_for_user(&mut self, user: UserId) { + self.data.user_keys.remove(&user); + } + + fn install_after_first_unlock_key_for_user( + &mut self, + user: UserId, + super_key: Arc, + ) -> Result<()> { + self.data + .add_key_to_key_index(&super_key) + .context(err!("add_key_to_key_index failed"))?; + self.data + .user_keys + .entry(user) + .or_default() + .after_first_unlock = Some(super_key); + Ok(()) + } + + fn lookup_key(&self, key_id: &SuperKeyIdentifier) -> Result>> { + Ok(match key_id { + SuperKeyIdentifier::DatabaseId(id) => { + self.data.key_index.get(id).and_then(|k| k.upgrade()) + } + SuperKeyIdentifier::BootLevel(level) => self + .data + .boot_level_key_cache + .as_ref() + .map(|b| b.lock().unwrap().aes_key(*level as usize)) + .transpose() + .context(err!("aes_key failed"))? + .flatten() + .map(|key| { + Arc::new(SuperKey { + algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + key, + id: *key_id, + reencrypt_with: None, + }) + }), + }) + } + + /// Returns the AfterFirstUnlock superencryption key for the given user ID, or None if the user + /// has not yet unlocked the device since boot. + pub fn get_after_first_unlock_key_by_user_id( + &self, + user_id: UserId, + ) -> Option> { + self.get_after_first_unlock_key_by_user_id_internal(user_id) + .map(|sk| -> Arc { sk }) + } + + fn get_after_first_unlock_key_by_user_id_internal( + &self, + user_id: UserId, + ) -> Option> { + self.data + .user_keys + .get(&user_id) + .and_then(|e| e.after_first_unlock.as_ref().cloned()) + } + + /// Check if a given key is super-encrypted, from its metadata. If so, unwrap the key using + /// the relevant super key. + pub fn unwrap_key_if_required<'a>( + &self, + metadata: &BlobMetaData, + blob: &'a [u8], + ) -> Result> { + Ok( + if let Some(key_id) = SuperKeyIdentifier::from_metadata(metadata) { + let super_key = self + .lookup_key(&key_id) + .context(err!("lookup_key failed"))? + .ok_or(Error::Rc(ResponseCode::LOCKED)) + .context(err!("Required super decryption key is not in memory."))?; + KeyBlob::Sensitive { + key: Self::unwrap_key_with_key(blob, metadata, &super_key) + .context(err!("unwrap_key_with_key failed"))?, + reencrypt_with: super_key + .reencrypt_with + .as_ref() + .unwrap_or(&super_key) + .clone(), + force_reencrypt: super_key.reencrypt_with.is_some(), + } + } else { + KeyBlob::Ref(blob) + }, + ) + } + + /// Unwraps an encrypted key blob given an encryption key. + fn unwrap_key_with_key(blob: &[u8], metadata: &BlobMetaData, key: &SuperKey) -> Result { + match key.algorithm { + SuperEncryptionAlgorithm::Aes256Gcm => match (metadata.iv(), metadata.aead_tag()) { + (Some(iv), Some(tag)) => key + .decrypt(blob, iv, tag) + .context(err!("Failed to decrypt the key blob.")), + (iv, tag) => Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(err!( + "Key has incomplete metadata. Present: iv: {}, aead_tag: {}.", + iv.is_some(), + tag.is_some(), + )), + }, + SuperEncryptionAlgorithm::EcdhP521 => { + match ( + metadata.public_key(), + metadata.salt(), + metadata.iv(), + metadata.aead_tag(), + ) { + (Some(public_key), Some(salt), Some(iv), Some(aead_tag)) => { + ECDHPrivateKey::from_private_key(&key.key) + .and_then(|k| k.decrypt_message(public_key, salt, iv, blob, aead_tag)) + .context(err!("Failed to decrypt the key blob with ECDH.")) + } + (public_key, salt, iv, aead_tag) => { + Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(err!( + concat!( + "Key has incomplete metadata. ", + "Present: public_key: {}, salt: {}, iv: {}, aead_tag: {}." + ), + public_key.is_some(), + salt.is_some(), + iv.is_some(), + aead_tag.is_some(), + )) + } + } + } + } + } + + /// Checks if the user's AfterFirstUnlock super key exists in the database (or legacy database). + /// The reference to self is unused but it is required to prevent calling this function + /// concurrently with skm state database changes. + fn super_key_exists_in_db_for_user( + &self, + db: &mut KeymasterDb, + user_id: UserId, + ) -> Result { + let key_in_db = db + .key_exists( + Domain::APP, + user_id as u64 as i64, + USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias, + KeyType::Super, + ) + .context(err!())?; + + Ok(key_in_db) + } + + // Helper function to populate super key cache from the super key blob loaded from the database. + fn populate_cache_from_super_key_blob( + &mut self, + user_id: UserId, + algorithm: SuperEncryptionAlgorithm, + entry: KeyEntry, + pw: &Password, + ) -> Result> { + let super_key = Self::extract_super_key_from_key_entry(algorithm, entry, pw, None) + .context(err!("Failed to extract super key from key entry"))?; + self.install_after_first_unlock_key_for_user(user_id, super_key.clone()) + .context(err!( + "Failed to install AfterFirstUnlock super key for user!" + ))?; + Ok(super_key) + } + + /// Extracts super key from the entry loaded from the database. + pub fn extract_super_key_from_key_entry( + algorithm: SuperEncryptionAlgorithm, + entry: KeyEntry, + pw: &Password, + reencrypt_with: Option>, + ) -> Result> { + if let Some((blob, metadata)) = entry.key_blob_info() { + let key = match ( + metadata.encrypted_by(), + metadata.salt(), + metadata.iv(), + metadata.aead_tag(), + ) { + (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => { + // Note that password encryption is AES no matter the value of algorithm. + let key = pw + .derive_key_hkdf(salt, AES_256_KEY_LENGTH) + .context(err!("Failed to derive key from password."))?; + + aes_gcm_decrypt(blob, iv, tag, &key).or_else(|_e| { + // Handle old key stored before the switch to HKDF. + let key = pw + .derive_key_pbkdf2(salt, AES_256_KEY_LENGTH) + .context(err!("Failed to derive key from password (PBKDF2)."))?; + aes_gcm_decrypt(blob, iv, tag, &key) + .context(err!("Failed to decrypt key blob.")) + })? + } + (enc_by, salt, iv, tag) => { + return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(err!( + concat!( + "Super key has incomplete metadata.", + "encrypted_by: {:?}; Present: salt: {}, iv: {}, aead_tag: {}." + ), + enc_by, + salt.is_some(), + iv.is_some(), + tag.is_some() + )); + } + }; + Ok(Arc::new(SuperKey { + algorithm, + key, + id: SuperKeyIdentifier::DatabaseId(entry.id()), + reencrypt_with, + })) + } else { + Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(err!("No key blob info.")) + } + } + + /// Encrypts the super key from a key derived from the password, before storing in the database. + /// This does not stretch the password; i.e., it assumes that the password is a high-entropy + /// synthetic password, not a low-entropy user provided password. + pub fn encrypt_with_password( + super_key: &[u8], + pw: &Password, + ) -> Result<(Vec, BlobMetaData)> { + let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?; + let derived_key = pw + .derive_key_hkdf(&salt, AES_256_KEY_LENGTH) + .context(err!("Failed to derive key from password."))?; + let mut metadata = BlobMetaData::new(); + metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); + metadata.add(BlobMetaEntry::Salt(salt)); + let (encrypted_key, iv, tag) = aes_gcm_encrypt(super_key, &derived_key) + .context(err!("Failed to encrypt new super key."))?; + metadata.add(BlobMetaEntry::Iv(iv)); + metadata.add(BlobMetaEntry::AeadTag(tag)); + Ok((encrypted_key, metadata)) + } + + // Helper function to encrypt a key with the given super key. Callers should select which super + // key to be used. This is called when a key is super encrypted at its creation as well as at + // its upgrade. + fn encrypt_with_aes_super_key( + key_blob: &[u8], + super_key: &SuperKey, + ) -> Result<(Vec, BlobMetaData)> { + if super_key.algorithm != SuperEncryptionAlgorithm::Aes256Gcm { + return Err(Error::sys()).context(err!("unexpected algorithm")); + } + let mut metadata = BlobMetaData::new(); + let (encrypted_key, iv, tag) = aes_gcm_encrypt(key_blob, &(super_key.key)) + .context(err!("Failed to encrypt new super key."))?; + metadata.add(BlobMetaEntry::Iv(iv)); + metadata.add(BlobMetaEntry::AeadTag(tag)); + super_key.id.add_to_metadata(&mut metadata); + Ok((encrypted_key, metadata)) + } + + // Encrypts a given key_blob using a hybrid approach, which can either use the symmetric super + // key or the public super key depending on which is available. + // + // If the symmetric_key is available, the key_blob is encrypted using symmetric encryption with + // the provided symmetric super key. Otherwise, the function loads the public super key from + // the KeymasterDb and encrypts the key_blob using ECDH encryption and marks the keyblob to be + // re-encrypted with the symmetric super key on the first use. + // + // This hybrid scheme allows keys that use the UnlockedDeviceRequired key parameter to be + // created while the device is locked. + fn encrypt_with_hybrid_super_key( + key_blob: &[u8], + symmetric_key: Option<&SuperKey>, + public_key_type: &SuperKeyType, + db: &mut KeymasterDb, + user_id: UserId, + ) -> Result<(Vec, BlobMetaData)> { + if let Some(super_key) = symmetric_key { + Self::encrypt_with_aes_super_key(key_blob, super_key).context(err!( + "Failed to encrypt with UnlockedDeviceRequired symmetric super key." + )) + } else { + // Symmetric key is not available, use public key encryption + let loaded = db + .load_super_key(public_key_type, user_id) + .context(err!("load_super_key failed."))?; + let (key_id_guard, key_entry) = loaded + .ok_or_else(Error::sys) + .context(err!("User ECDH super key missing."))?; + let public_key = key_entry + .metadata() + .sec1_public_key() + .ok_or_else(Error::sys) + .context(err!("sec1_public_key missing."))?; + let mut metadata = BlobMetaData::new(); + let (ephem_key, salt, iv, encrypted_key, aead_tag) = + ECDHPrivateKey::encrypt_message(public_key, key_blob) + .context(err!("ECDHPrivateKey::encrypt_message failed."))?; + metadata.add(BlobMetaEntry::PublicKey(ephem_key)); + metadata.add(BlobMetaEntry::Salt(salt)); + metadata.add(BlobMetaEntry::Iv(iv)); + metadata.add(BlobMetaEntry::AeadTag(aead_tag)); + SuperKeyIdentifier::DatabaseId(key_id_guard.id()).add_to_metadata(&mut metadata); + Ok((encrypted_key, metadata)) + } + } + + /// Check if super encryption is required and if so, super-encrypt the key to be stored in + /// the database. + #[allow(clippy::too_many_arguments)] + pub fn handle_super_encryption_on_key_init( + &self, + db: &mut KeymasterDb, + domain: &Domain, + key_parameters: &[KeyParameter], + flags: Option, + user_id: UserId, + key_blob: &[u8], + ) -> Result<(Vec, BlobMetaData)> { + match Enforcements::super_encryption_required(domain, key_parameters, flags) { + SuperEncryptionType::None => Ok((key_blob.to_vec(), BlobMetaData::new())), + SuperEncryptionType::AfterFirstUnlock => { + // Encrypt the given key blob with the user's AfterFirstUnlock super key. If the + // user has not unlocked the device since boot or the super keys were never + // initialized for the user for some reason, an error is returned. + match self + .get_user_state(db, user_id) + .context(err!("Failed to get user state for user {user_id}"))? + { + UserState::AfterFirstUnlock(super_key) => { + Self::encrypt_with_aes_super_key(key_blob, &super_key).context(err!( + "Failed to encrypt with AfterFirstUnlock super key for user {user_id}" + )) + } + UserState::BeforeFirstUnlock => { + Err(Error::Rc(ResponseCode::LOCKED)).context(err!("Device is locked.")) + } + UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED)) + .context(err!("User {user_id} does not have super keys")), + } + } + SuperEncryptionType::UnlockedDeviceRequired => { + let symmetric_key = self + .data + .user_keys + .get(&user_id) + .and_then(|e| e.unlocked_device_required_symmetric.as_ref()) + .map(|arc| arc.as_ref()); + Self::encrypt_with_hybrid_super_key( + key_blob, + symmetric_key, + &USER_UNLOCKED_DEVICE_REQUIRED_P521_SUPER_KEY, + db, + user_id, + ) + .context(err!( + "Failed to encrypt with UnlockedDeviceRequired hybrid scheme." + )) + } + SuperEncryptionType::BootLevel(level) => { + let key_id = SuperKeyIdentifier::BootLevel(level); + let super_key = self + .lookup_key(&key_id) + .context(err!("lookup_key failed"))? + .ok_or(Error::Rc(ResponseCode::LOCKED)) + .context(err!("Boot stage key absent"))?; + Self::encrypt_with_aes_super_key(key_blob, &super_key) + .context(err!("Failed to encrypt with BootLevel key.")) + } + } + } + + /// Check if a given key needs re-super-encryption, from its KeyBlob type. + /// If so, re-super-encrypt the key and return a new set of metadata, + /// containing the new super encryption information. + pub fn reencrypt_if_required<'a>( + key_blob_before_upgrade: &KeyBlob, + key_after_upgrade: &'a [u8], + ) -> Result<(KeyBlob<'a>, Option)> { + match key_blob_before_upgrade { + KeyBlob::Sensitive { + reencrypt_with: super_key, + .. + } => { + let (key, metadata) = + Self::encrypt_with_aes_super_key(key_after_upgrade, super_key) + .context(err!("Failed to re-super-encrypt key."))?; + Ok((KeyBlob::NonSensitive(key), Some(metadata))) + } + _ => Ok((KeyBlob::Ref(key_after_upgrade), None)), + } + } + + fn create_super_key( + &mut self, + db: &mut KeymasterDb, + user_id: UserId, + key_type: &SuperKeyType, + password: &Password, + reencrypt_with: Option>, + ) -> Result> { + log::info!("Creating {} for user {}", key_type.name, user_id); + let (super_key, public_key) = match key_type.algorithm { + SuperEncryptionAlgorithm::Aes256Gcm => ( + generate_aes256_key().context(err!("Failed to generate AES-256 key."))?, + None, + ), + SuperEncryptionAlgorithm::EcdhP521 => { + let key = + ECDHPrivateKey::generate().context(err!("Failed to generate ECDH key"))?; + ( + key.private_key().context(err!("private_key failed"))?, + Some(key.public_key().context(err!("public_key failed"))?), + ) + } + }; + // Derive an AES-256 key from the password and re-encrypt the super key before we insert it + // in the database. + let (encrypted_super_key, blob_metadata) = + Self::encrypt_with_password(&super_key, password).context(err!())?; + let mut key_metadata = KeyMetaData::new(); + if let Some(pk) = public_key { + key_metadata.add(KeyMetaEntry::Sec1PublicKey(pk)); + } + let key_entry = db + .store_super_key( + user_id, + key_type, + &encrypted_super_key, + &blob_metadata, + &key_metadata, + ) + .context(err!("Failed to store super key."))?; + Ok(Arc::new(SuperKey { + algorithm: key_type.algorithm, + key: super_key, + id: SuperKeyIdentifier::DatabaseId(key_entry.id()), + reencrypt_with, + })) + } + + /// Fetch a superencryption key from the database, or create it if it doesn't already exist. + /// When this is called, the caller must hold the lock on the SuperKeyManager. + /// So it's OK that the check and creation are different DB transactions. + fn get_or_create_super_key( + &mut self, + db: &mut KeymasterDb, + user_id: UserId, + key_type: &SuperKeyType, + password: &Password, + reencrypt_with: Option>, + ) -> Result> { + let loaded_key = db.load_super_key(key_type, user_id)?; + if let Some((_, key_entry)) = loaded_key { + Ok(Self::extract_super_key_from_key_entry( + key_type.algorithm, + key_entry, + password, + reencrypt_with, + )?) + } else { + self.create_super_key(db, user_id, key_type, password, reencrypt_with) + } + } + + /// Decrypt the UnlockedDeviceRequired super keys for this user using the password and store + /// them in memory. If these keys don't exist yet, create them. + pub fn unlock_unlocked_device_required_keys( + &mut self, + db: &mut KeymasterDb, + user_id: UserId, + password: &Password, + ) -> Result<()> { + let (symmetric, private) = self + .data + .user_keys + .get(&user_id) + .map(|e| { + ( + e.unlocked_device_required_symmetric.clone(), + e.unlocked_device_required_private.clone(), + ) + }) + .unwrap_or((None, None)); + + if symmetric.is_some() && private.is_some() { + // Already unlocked. + return Ok(()); + } + + let aes = if let Some(symmetric) = symmetric { + // This is weird. If this point is reached only one of the UnlockedDeviceRequired super + // keys was initialized. This should never happen. + symmetric + } else { + self.get_or_create_super_key( + db, + user_id, + &USER_UNLOCKED_DEVICE_REQUIRED_SYMMETRIC_SUPER_KEY, + password, + None, + ) + .context(err!("Trying to get or create symmetric key."))? + }; + + let ecdh = if let Some(private) = private { + // This is weird. If this point is reached only one of the UnlockedDeviceRequired super + // keys was initialized. This should never happen. + private + } else { + self.get_or_create_super_key( + db, + user_id, + &USER_UNLOCKED_DEVICE_REQUIRED_P521_SUPER_KEY, + password, + Some(aes.clone()), + ) + .context(err!("Trying to get or create asymmetric key."))? + }; + + self.data.add_key_to_key_index(&aes)?; + self.data.add_key_to_key_index(&ecdh)?; + let entry = self.data.user_keys.entry(user_id).or_default(); + entry.unlocked_device_required_symmetric = Some(aes); + entry.unlocked_device_required_private = Some(ecdh); + Ok(()) + } + + /// Protects the user's UnlockedDeviceRequired super keys in a way such that they can only be + /// unlocked by the enabled unlock methods. + pub fn lock_unlocked_device_required_keys( + &mut self, + db: &mut KeymasterDb, + user_id: UserId, + unlocking_sids: &[i64], + weak_unlock_enabled: bool, + ) { + let entry = self.data.user_keys.entry(user_id).or_default(); + if unlocking_sids.is_empty() { + entry.biometric_unlock = None; + } else if let (Some(aes), Some(ecdh)) = ( + entry.unlocked_device_required_symmetric.as_ref().cloned(), + entry.unlocked_device_required_private.as_ref().cloned(), + ) { + // If class 3 biometric unlock methods are enabled, create a biometric-encrypted copy of + // the keys. Do this even if weak unlock methods are enabled too; in that case we'll + // also retain a plaintext copy of the keys, but that copy will be wiped later if weak + // unlock methods expire. So we need the biometric-encrypted copy too just in case. + let res = (|| -> Result<()> { + let key_desc = + KeyMintDevice::internal_descriptor(format!("biometric_unlock_key_{}", user_id)); + let encrypting_key = generate_aes256_key()?; + let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) + .context(err!("KeyMintDevice::get failed"))?; + let mut key_params = vec![ + KeyParameterValue::Algorithm(Algorithm::AES), + KeyParameterValue::KeySize(256), + KeyParameterValue::BlockMode(BlockMode::GCM), + KeyParameterValue::PaddingMode(PaddingMode::NONE), + KeyParameterValue::CallerNonce, + KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT), + KeyParameterValue::MinMacLength(128), + KeyParameterValue::AuthTimeout(BIOMETRIC_AUTH_TIMEOUT_S), + KeyParameterValue::HardwareAuthenticatorType( + HardwareAuthenticatorType::FINGERPRINT, + ), + ]; + for sid in unlocking_sids { + key_params.push(KeyParameterValue::UserSecureID(*sid)); + } + let key_params: Vec = + key_params.into_iter().map(|x| x.into()).collect(); + km_dev.create_and_store_key( + db, + &key_desc, + KeyType::Client, /* TODO Should be Super b/189470584 */ + |dev| { + let _wp = + wd::watch("SKM::lock_unlocked_device_required_keys: calling IKeyMintDevice::importKey."); + dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None) + }, + )?; + entry.biometric_unlock = Some(BiometricUnlock { + sids: unlocking_sids.into(), + key_desc, + symmetric: LockedKey::new(&encrypting_key, &aes)?, + private: LockedKey::new(&encrypting_key, &ecdh)?, + }); + Ok(()) + })(); + if let Err(e) = res { + log::error!("Error setting up biometric unlock: {:#?}", e); + // The caller can't do anything about the error, and for security reasons we still + // wipe the keys (unless a weak unlock method is enabled). So just log the error. + } + } + // Wipe the plaintext copy of the keys, unless a weak unlock method is enabled. + if !weak_unlock_enabled { + entry.unlocked_device_required_symmetric = None; + entry.unlocked_device_required_private = None; + } + Self::log_status_of_unlocked_device_required_keys(user_id, entry); + } + + pub fn wipe_plaintext_unlocked_device_required_keys(&mut self, user_id: UserId) { + let entry = self.data.user_keys.entry(user_id).or_default(); + entry.unlocked_device_required_symmetric = None; + entry.unlocked_device_required_private = None; + Self::log_status_of_unlocked_device_required_keys(user_id, entry); + } + + pub fn wipe_all_unlocked_device_required_keys(&mut self, user_id: UserId) { + let entry = self.data.user_keys.entry(user_id).or_default(); + entry.unlocked_device_required_symmetric = None; + entry.unlocked_device_required_private = None; + entry.biometric_unlock = None; + Self::log_status_of_unlocked_device_required_keys(user_id, entry); + } + + fn log_status_of_unlocked_device_required_keys(user_id: UserId, entry: &UserSuperKeys) { + let status = match ( + // Note: the status of the symmetric and private keys should always be in sync. + // So we only check one here. + entry.unlocked_device_required_symmetric.is_some(), + entry.biometric_unlock.is_some(), + ) { + (false, false) => "fully protected", + (false, true) => "biometric-encrypted", + (true, false) => "retained in plaintext", + (true, true) => "retained in plaintext, with biometric-encrypted copy too", + }; + log::info!("UnlockedDeviceRequired super keys for user {user_id} are {status}."); + } + + /// User has unlocked, not using a password. See if any of our stored auth tokens can be used + /// to unlock the keys protecting UNLOCKED_DEVICE_REQUIRED keys. + pub fn try_unlock_user_with_biometric( + &mut self, + db: &mut KeymasterDb, + user_id: UserId, + ) -> Result<()> { + let entry = self.data.user_keys.entry(user_id).or_default(); + if entry.unlocked_device_required_symmetric.is_some() + && entry.unlocked_device_required_private.is_some() + { + // If the keys are already cached in plaintext, then there is no need to decrypt the + // biometric-encrypted copy. Both copies can be present here if the user has both + // class 3 biometric and weak unlock methods enabled, and the device was unlocked before + // the weak unlock methods expired. + return Ok(()); + } + if let Some(biometric) = entry.biometric_unlock.as_ref() { + let (key_id_guard, key_entry) = db + .load_key_entry( + &biometric.key_desc, + KeyType::Client, // This should not be a Client key. + KeyEntryLoadBits::KM, + AID_KEYSTORE, + ) + .context(err!("load_key_entry failed"))?; + let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) + .context(err!("KeyMintDevice::get failed"))?; + let mut errs = vec![]; + for sid in &biometric.sids { + let sid = *sid; + if let Some(auth_token_entry) = db.find_auth_token_entry(|entry| { + entry.auth_token().userId == sid || entry.auth_token().authenticatorId == sid + }) { + let res: Result<(Arc, Arc)> = (|| { + let symmetric = biometric.symmetric.decrypt( + db, + &km_dev, + &key_id_guard, + &key_entry, + auth_token_entry.auth_token(), + None, + )?; + let private = biometric.private.decrypt( + db, + &km_dev, + &key_id_guard, + &key_entry, + auth_token_entry.auth_token(), + Some(symmetric.clone()), + )?; + Ok((symmetric, private)) + })(); + match res { + Ok((symmetric, private)) => { + entry.unlocked_device_required_symmetric = Some(symmetric.clone()); + entry.unlocked_device_required_private = Some(private.clone()); + self.data.add_key_to_key_index(&symmetric)?; + self.data.add_key_to_key_index(&private)?; + log::info!("Successfully unlocked user {user_id} with biometric {sid}",); + return Ok(()); + } + Err(e) => { + // Don't log an error yet, as some other biometric SID might work. + errs.push((sid, e)); + } + } + } + } + if !errs.is_empty() { + log::warn!("biometric unlock failed for all SIDs, with errors:"); + for (sid, err) in errs { + log::warn!(" biometric {sid}: {err}"); + } + } + } + Ok(()) + } + + /// Returns the keystore locked state of the given user. It requires the thread local + /// keystore database and a reference to the legacy migrator because it may need to + /// import the super key from the legacy blob database to the keystore database. + pub fn get_user_state(&self, db: &mut KeymasterDb, user_id: UserId) -> Result { + match self.get_after_first_unlock_key_by_user_id_internal(user_id) { + Some(super_key) => Ok(UserState::AfterFirstUnlock(super_key)), + None => { + // Check if a super key exists in the database or legacy database. + // If so, return locked user state. + if self + .super_key_exists_in_db_for_user(db, user_id) + .context(err!())? + { + Ok(UserState::BeforeFirstUnlock) + } else { + Ok(UserState::Uninitialized) + } + } + } + } + + /// Deletes all keys and super keys for the given user. + /// This is called when a user is deleted. + pub fn remove_user(&mut self, db: &mut KeymasterDb, user_id: UserId) -> Result<()> { + log::info!("remove_user(user={user_id})"); + // Mark keys created on behalf of the user as unreferenced. + db.unbind_keys_for_user(user_id) + .context(err!("Error in unbinding keys."))?; + + // Delete super key in cache, if exists. + self.forget_all_keys_for_user(user_id); + Ok(()) + } + + /// Initializes the given user by creating their super keys, both AfterFirstUnlock and + /// UnlockedDeviceRequired. If allow_existing is true, then the user already being initialized + /// is not considered an error. + pub fn initialize_user( + &mut self, + db: &mut KeymasterDb, + user_id: UserId, + password: &Password, + allow_existing: bool, + ) -> Result<()> { + // Create the AfterFirstUnlock super key. + if self.super_key_exists_in_db_for_user(db, user_id)? { + log::info!("AfterFirstUnlock super key already exists"); + if !allow_existing { + return Err(Error::sys()).context(err!("Tried to re-init an initialized user!")); + } + } else { + let super_key = self + .create_super_key( + db, + user_id, + &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, + password, + None, + ) + .context(err!("Failed to create AfterFirstUnlock super key"))?; + + self.install_after_first_unlock_key_for_user(user_id, super_key) + .context(err!( + "Failed to install AfterFirstUnlock super key for user" + ))?; + } + + // Create the UnlockedDeviceRequired super keys. + self.unlock_unlocked_device_required_keys(db, user_id, password) + .context(err!("Failed to create UnlockedDeviceRequired super keys")) + } + + /// Unlocks the given user with the given password. + /// + /// If the user state is BeforeFirstUnlock: + /// - Unlock the user's AfterFirstUnlock super key + /// - Unlock the user's UnlockedDeviceRequired super keys + /// + /// If the user state is AfterFirstUnlock: + /// - Unlock the user's UnlockedDeviceRequired super keys only + /// + pub fn unlock_user( + &mut self, + db: &mut KeymasterDb, + user_id: UserId, + password: &Password, + ) -> Result<()> { + log::info!("unlock_user(user={user_id})"); + match self.get_user_state(db, user_id)? { + UserState::AfterFirstUnlock(_) => { + self.unlock_unlocked_device_required_keys(db, user_id, password) + } + UserState::Uninitialized => { + Err(Error::sys()).context(err!("Tried to unlock an uninitialized user!")) + } + UserState::BeforeFirstUnlock => { + let alias = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY; + let result = db.load_super_key(alias, user_id)?; + + match result { + Some((_, entry)) => { + self.populate_cache_from_super_key_blob( + user_id, + alias.algorithm, + entry, + password, + ) + .context(err!("Failed when unlocking user."))?; + self.unlock_unlocked_device_required_keys(db, user_id, password) + } + None => { + Err(Error::sys()).context(err!("Locked user does not have a super key!")) + } + } + } + } + } +} + +/// This enum represents different states of the user's life cycle in the device. +/// For now, only three states are defined. More states may be added later. +pub enum UserState { + // The user's super keys exist, and the user has unlocked the device at least once since boot. + // Hence, the AfterFirstUnlock super key is available in the cache. + AfterFirstUnlock(Arc), + // The user's super keys exist, but the user hasn't unlocked the device at least once since + // boot. Hence, the AfterFirstUnlock and UnlockedDeviceRequired super keys are not available in + // the cache. However, they exist in the database in encrypted form. + BeforeFirstUnlock, + // The user's super keys don't exist. I.e., there's no user with the given user ID, or the user + // is in the process of being created or destroyed. + Uninitialized, +} + +/// This enum represents three states a KeyMint Blob can be in, w.r.t super encryption. +/// `Sensitive` holds the non encrypted key and a reference to its super key. +/// `NonSensitive` holds a non encrypted key that is never supposed to be encrypted. +/// `Ref` holds a reference to a key blob when it does not need to be modified if its +/// life time allows it. +pub enum KeyBlob<'a> { + Sensitive { + key: ZVec, + /// If KeyMint reports that the key must be upgraded, we must + /// re-encrypt the key before writing to the database; we use + /// this key. + reencrypt_with: Arc, + /// If this key was decrypted with an ECDH key, we want to + /// re-encrypt it on first use whether it was upgraded or not; + /// this field indicates that that's necessary. + force_reencrypt: bool, + }, + NonSensitive(Vec), + Ref(&'a [u8]), +} + +impl KeyBlob<'_> { + pub fn force_reencrypt(&self) -> bool { + if let KeyBlob::Sensitive { + force_reencrypt, .. + } = self + { + *force_reencrypt + } else { + false + } + } +} + +/// Deref returns a reference to the key material in any variant. +impl Deref for KeyBlob<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + Self::Sensitive { key, .. } => key, + Self::NonSensitive(key) => key, + Self::Ref(key) => key, + } + } +} diff --git a/libs/rust/src/keymaster/utils.rs b/libs/rust/src/keymaster/utils.rs new file mode 100644 index 0000000..82e6178 --- /dev/null +++ b/libs/rust/src/keymaster/utils.rs @@ -0,0 +1,136 @@ +use crate::{ + android::hardware::security::keymint::{ + ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, + KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, + }, + consts, err, + keymaster::{ + error::KsError as Error, key_parameter::KeyParameter, keymint_device::KeyMintDevice, + }, + watchdog, +}; + +use anyhow::{Context, Result}; + +pub fn log_params(params: &[KmKeyParameter]) -> Vec { + params.iter().cloned().collect::>() +} + +/// Converts a set of key characteristics as returned from KeyMint into the internal +/// representation of the keystore service. +pub fn key_characteristics_to_internal( + key_characteristics: Vec, +) -> Vec { + key_characteristics + .into_iter() + .flat_map(|aidl_key_char| { + let sec_level = aidl_key_char.securityLevel; + aidl_key_char + .authorizations + .into_iter() + .map(move |aidl_kp| KeyParameter::new(aidl_kp.into(), sec_level)) + }) + .collect() +} + +/// Upgrade a keyblob then invoke both the `new_blob_handler` and the `km_op` closures. On success +/// a tuple of the `km_op`s result and the optional upgraded blob is returned. +fn upgrade_keyblob_and_perform_op( + km_dev: &dyn IKeyMintDevice, + key_blob: &[u8], + upgrade_params: &[KmKeyParameter], + km_op: KmOp, + new_blob_handler: NewBlobHandler, +) -> Result<(T, Option>)> +where + KmOp: Fn(&[u8]) -> Result, + NewBlobHandler: FnOnce(&[u8]) -> Result<()>, +{ + let upgraded_blob = { + let _wp = watchdog::watch( + "utils::upgrade_keyblob_and_perform_op: calling IKeyMintDevice::upgradeKey.", + ); + km_dev.upgradeKey(key_blob, upgrade_params) + } + .context(err!("Upgrade failed."))?; + + new_blob_handler(&upgraded_blob).context(err!("calling new_blob_handler."))?; + + km_op(&upgraded_blob) + .map(|v| (v, Some(upgraded_blob))) + .context(err!("Calling km_op after upgrade.")) +} + +/// This function can be used to upgrade key blobs on demand. The return value of +/// `km_op` is inspected and if ErrorCode::KEY_REQUIRES_UPGRADE is encountered, +/// an attempt is made to upgrade the key blob. On success `new_blob_handler` is called +/// with the upgraded blob as argument. Then `km_op` is called a second time with the +/// upgraded blob as argument. On success a tuple of the `km_op`s result and the +/// optional upgraded blob is returned. +pub fn upgrade_keyblob_if_required_with( + km_dev: &dyn IKeyMintDevice, + km_dev_version: i32, + key_blob: &[u8], + upgrade_params: &[KmKeyParameter], + km_op: KmOp, + new_blob_handler: NewBlobHandler, +) -> Result<(T, Option>)> +where + KmOp: Fn(&[u8]) -> Result, + NewBlobHandler: FnOnce(&[u8]) -> Result<()>, +{ + match km_op(key_blob) { + Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => upgrade_keyblob_and_perform_op( + km_dev, + key_blob, + upgrade_params, + km_op, + new_blob_handler, + ), + Err(Error::Km(ErrorCode::INVALID_KEY_BLOB)) + if km_dev_version >= KeyMintDevice::KEY_MINT_V1 => + { + // A KeyMint (not Keymaster via km_compat) device says that this is an invalid keyblob. + // + // This may be because the keyblob was created before an Android upgrade, and as part of + // the device upgrade the underlying Keymaster/KeyMint implementation has been upgraded. + // + // If that's the case, there are three possible scenarios: + if key_blob.starts_with(consts::KEYMASTER_BLOB_HW_PREFIX) { + // 1) The keyblob was created in hardware by the km_compat C++ code, using a prior + // Keymaster implementation, and wrapped. + // + // In this case, the keyblob will have the km_compat magic prefix, including the + // marker that indicates that this was a hardware-backed key. + // + // The inner keyblob should still be recognized by the hardware implementation, so + // strip the prefix and attempt a key upgrade. + log::info!( + "found apparent km_compat(Keymaster) HW blob, attempt strip-and-upgrade" + ); + let inner_keyblob = &key_blob[consts::KEYMASTER_BLOB_HW_PREFIX.len()..]; + upgrade_keyblob_and_perform_op( + km_dev, + inner_keyblob, + upgrade_params, + km_op, + new_blob_handler, + ) + } else { + Err(Error::Km(ErrorCode::INVALID_KEY_BLOB)).context(err!("Calling km_op")) + } + } + r => r.map(|v| (v, None)).context(err!("Calling km_op.")), + } +} + +/// Converts a set of key characteristics from the internal representation into a set of +/// Authorizations as they are used to convey key characteristics to the clients of keystore. +pub fn key_parameters_to_authorizations( + parameters: Vec, +) -> Vec { + parameters + .into_iter() + .map(|p| p.into_authorization()) + .collect() +} diff --git a/libs/rust/src/keymint/clock.rs b/libs/rust/src/keymint/clock.rs index 6e9759a..1d4af52 100644 --- a/libs/rust/src/keymint/clock.rs +++ b/libs/rust/src/keymint/clock.rs @@ -30,7 +30,10 @@ impl StdClock { impl crypto::MonotonicClock for StdClock { fn now(&self) -> crypto::MillisecondsSinceEpoch { - let mut time = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let mut time = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; // Use `CLOCK_BOOTTIME` for consistency with the times used by the Cuttlefish // C++ implementation of Gatekeeper. let rc = diff --git a/libs/rust/src/keymint/mod.rs b/libs/rust/src/keymint/mod.rs index fe93f4f..c30cd90 100644 --- a/libs/rust/src/keymint/mod.rs +++ b/libs/rust/src/keymint/mod.rs @@ -2,4 +2,4 @@ pub mod attest; pub mod clock; pub mod rpc; pub mod sdd; -pub mod soft; \ No newline at end of file +pub mod soft; diff --git a/libs/rust/src/keymint/sdd.rs b/libs/rust/src/keymint/sdd.rs index 9e8f05f..25ce988 100644 --- a/libs/rust/src/keymint/sdd.rs +++ b/libs/rust/src/keymint/sdd.rs @@ -19,6 +19,7 @@ use crate::proto::storage; use kmr_common::{crypto, keyblob, km_err, Error}; +use log::error; use log::info; use prost::Message; use std::fs; @@ -26,8 +27,6 @@ use std::io::BufRead; use std::io::Write; use std::path; -use crate::error; - const SECURE_DELETION_DATA_FILE: &str = "./omk/data/keymint_secure_deletion_data"; #[cfg(target_os = "android")] @@ -59,14 +58,15 @@ fn read_sdd_file() -> Result { } fn write_sdd_file(data: &storage::SecureDeletionData) -> Result<(), Error> { - fs::create_dir_all(path::Path::new(SECURE_DELETION_DATA_FILE).parent().unwrap()) - .map_err(|e| { + fs::create_dir_all(path::Path::new(SECURE_DELETION_DATA_FILE).parent().unwrap()).map_err( + |e| { km_err!( SecureHwCommunicationFailed, "failed to create directory for secure deletion data file: {:?}", e ) - })?; + }, + )?; let mut f = fs::File::create(SECURE_DELETION_DATA_FILE).map_err(|e| { km_err!( SecureHwCommunicationFailed, diff --git a/libs/rust/src/lib.rs b/libs/rust/src/lib.rs index e69de29..82eb304 100644 --- a/libs/rust/src/lib.rs +++ b/libs/rust/src/lib.rs @@ -0,0 +1,62 @@ +#![recursion_limit = "256"] + +use anyhow::Context; +use lazy_static; +use std::sync::{Arc, Mutex}; + +use jni::JavaVM; +use log::debug; + +pub mod consts; +pub mod global; +pub mod keymaster; +pub mod keymint; +pub mod logging; +pub mod macros; +pub mod plat; +pub mod proto; +pub mod utils; +pub mod watchdog; + +include!(concat!(env!("OUT_DIR"), "/aidl.rs")); + +lazy_static::lazy_static! { + static ref JAVA_VM: Arc>> = Arc::new(Mutex::new(None)); +} + +#[no_mangle] +pub extern "C" fn init(_env: jni::JNIEnv, _class: jni::objects::JClass) { + debug!("nativeInit called"); + // You can add more initialization code here if needed +} + +#[no_mangle] +pub extern "C" fn JNI_OnLoad( + vm: *mut jni::sys::JavaVM, + _reserved: *mut std::ffi::c_void, +) -> jni::sys::jint { + let jvm = unsafe { jni::JavaVM::from_raw(vm).expect("Failed to get JavaVM from raw pointer") }; + let mut env = jvm.get_env().unwrap(); + + logging::init_logger(); + rsbinder::ProcessState::init_default(); + debug!("Hello, OhMyKeymint!"); + + let class = env + .find_class("top/qwq2333/ohmykeymint/Native") + .context("Failed to find class top/qwq2333/ohmykeymint/Native") + .unwrap(); + + let methods = jni_methods![["nativeInit", "()V", init], ["init", "()V", init],]; + + debug!("Registering native methods"); + env.register_native_methods(class, methods.as_slice()) + .context("Failed to register native methods") + .unwrap(); + + debug!("Saving JavaVM instance"); + let mut java_vm_lock = JAVA_VM.lock().unwrap(); + *java_vm_lock = Some(jvm); + + return jni::sys::JNI_VERSION_1_6; +} diff --git a/libs/rust/src/logging.rs b/libs/rust/src/logging.rs index 502f9ce..7c2cb7e 100644 --- a/libs/rust/src/logging.rs +++ b/libs/rust/src/logging.rs @@ -16,3 +16,15 @@ pub fn init_logger() { .unwrap(); log4rs::init_config(config).unwrap(); } + +#[cfg(target_os = "android")] +use android_logger::Config; + +#[cfg(target_os = "android")] +pub fn init_logger() { + android_logger::init_once( + Config::default() + .with_max_level(LevelFilter::Trace) + .with_tag("OhMyKeymint"), + ); +} diff --git a/libs/rust/src/macros.rs b/libs/rust/src/macros.rs index f0be221..2821f9b 100644 --- a/libs/rust/src/macros.rs +++ b/libs/rust/src/macros.rs @@ -1,31 +1,35 @@ #[macro_export] -#[cfg(target_os = "android")] -macro_rules! logd { - ($tag:expr, $msg:expr) => { - android_logger_lite::d($tag.to_string(), $msg.to_string()) +macro_rules! jni_methods { + ($([$method:expr, $signature:expr, $fn:expr]),* $(,)?) => { + vec![ + $( + { + jni::NativeMethod { + name: jni::strings::JNIString::from($method), + sig: jni::strings::JNIString::from($signature), + fn_ptr: $fn as *mut _, + } + } + ),* + ] }; } +/// Generates a message containing the current source file name and line number. +/// +/// # Examples +/// +/// ``` +/// source_location_msg!("Key is expired."); +/// Result: +/// "src/lib.rs:7 Key is expired." +/// ``` #[macro_export] -#[cfg(target_os = "android")] -macro_rules! logi { - ($tag:expr, $msg:expr) => { - android_logger_lite::i($tag.to_string(), $msg.to_string()) +macro_rules! err { + { $($arg:tt)+ } => { + format!("{}:{} {}", file!(), line!(), format_args!($($arg)+)) }; -} - -#[macro_export] -#[cfg(target_os = "android")] -macro_rules! logw { - ($tag:expr, $msg:expr) => { - android_logger_lite::w($tag.to_string(), $msg.to_string()) - }; -} - -#[macro_export] -#[cfg(target_os = "android")] -macro_rules! loge { - ($tag:expr, $msg:expr) => { - android_logger_lite::e($tag.to_string(), $msg.to_string()) + {} => { + format!("{}:{}", file!(), line!()) }; } diff --git a/libs/rust/src/main.rs b/libs/rust/src/main.rs index 1f604ba..c12a4d2 100644 --- a/libs/rust/src/main.rs +++ b/libs/rust/src/main.rs @@ -1,19 +1,42 @@ +#![recursion_limit = "256"] + +use std::io::Read; + use kmr_common::crypto::{self, MonotonicClock}; use kmr_crypto_boring::{ - aes::BoringAes, aes_cmac::BoringAesCmac, des::BoringDes, ec::BoringEc, eq::BoringEq, hmac::BoringHmac, rng::BoringRng, rsa::BoringRsa, sha256::BoringSha256 + aes::BoringAes, aes_cmac::BoringAesCmac, des::BoringDes, ec::BoringEc, eq::BoringEq, + hmac::BoringHmac, rng::BoringRng, rsa::BoringRsa, sha256::BoringSha256, +}; +use kmr_ta::{ + device::{CsrSigningAlgorithm, Implementation}, + HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3, }; -use kmr_ta::{device::{CsrSigningAlgorithm, Implementation}, HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; -use kmr_wire::{keymint::{DateTime, KeyCharacteristics, KeyParam, KeyPurpose, SecurityLevel}, rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR, GenerateKeyRequest, GetHardwareInfoRequest, KeySizeInBits, PerformOpReq}; -use log::{debug, error}; +use kmr_wire::{ + keymint::{DateTime, KeyParam, KeyPurpose, SecurityLevel}, + rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR, + GenerateKeyRequest, GetHardwareInfoRequest, KeySizeInBits, PerformOpReq, +}; +use log::{debug, error, info}; +use rsbinder::Parcelable; -use crate::keymint::{attest, clock, sdd, soft}; +use crate::{ + android::system::keystore2::{ + CreateOperationResponse::CreateOperationResponse, IKeystoreOperation, KeyDescriptor, + }, + keymint::{attest, clock, sdd, soft}, + utils::ParcelExt, +}; -pub mod macros; -pub mod proto; -pub mod keymint; +pub mod consts; +pub mod global; pub mod keymaster; +pub mod keymint; pub mod logging; +pub mod macros; +pub mod plat; +pub mod proto; pub mod utils; +pub mod watchdog; include!(concat!(env!("OUT_DIR"), "/aidl.rs")); // include!( "./aidl.rs"); // for development only @@ -21,13 +44,16 @@ include!(concat!(env!("OUT_DIR"), "/aidl.rs")); #[cfg(target_os = "android")] const TAG: &str = "OhMyKeymint"; -fn main(){ - debug!("Hello, OhMyKeymint!"); +fn main() { logging::init_logger(); + rsbinder::ProcessState::init_default(); let db = keymaster::db::KeymasterDb::new().unwrap(); db.close().unwrap(); - #[cfg(target_os = "android")] logi!(TAG, "Application started"); + debug!("Hello, OhMyKeymint!"); + + #[cfg(target_os = "android")] + logi!(TAG, "Application started"); let security_level: SecurityLevel = SecurityLevel::TrustedEnvironment; let hw_info = HardwareInfo { version_number: 2, @@ -73,7 +99,10 @@ fn main(){ }; let keys: Box = Box::new(soft::Keys); - let rpc: Box = Box::new(soft::RpcArtifacts::new(soft::Derive::default(), rpc_sign_algo)); + let rpc: Box = Box::new(soft::RpcArtifacts::new( + soft::Derive::default(), + rpc_sign_algo, + )); let dev = Implementation { keys, @@ -99,16 +128,16 @@ fn main(){ let mut ta = KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev); - let req = PerformOpReq::DeviceGetHardwareInfo(GetHardwareInfoRequest{}); + let req = PerformOpReq::DeviceGetHardwareInfo(GetHardwareInfoRequest {}); let resp = ta.process_req(req); debug!("GetHardwareInfo response: {:?}", resp); let req = PerformOpReq::SetBootInfo(kmr_wire::SetBootInfoRequest { - verified_boot_state: 0, // Verified - verified_boot_hash: vec![0; 32], - verified_boot_key: vec![0; 32], - device_boot_locked: true, - boot_patchlevel: 20250605, + verified_boot_state: 0, // Verified + verified_boot_hash: vec![0; 32], + verified_boot_key: vec![0; 32], + device_boot_locked: true, + boot_patchlevel: 20250605, }); let resp = ta.process_req(req); debug!("SetBootInfo response: {:?}", resp); @@ -121,9 +150,7 @@ fn main(){ let resp = ta.process_req(req); debug!("SetHalInfo response: {:?}", resp); - let req = PerformOpReq::SetHalVersion(kmr_wire::SetHalVersionRequest { - aidl_version: 400, - }); + let req = PerformOpReq::SetHalVersion(kmr_wire::SetHalVersionRequest { aidl_version: 400 }); let resp = ta.process_req(req); debug!("SetHalVersion response: {:?}", resp); @@ -138,35 +165,38 @@ fn main(){ imei: "350505563694821".into(), imei2: "350505563694822".into(), meid: "350505563694823".into(), - } + }, }); let resp = ta.process_req(req); debug!("SetAttestationIds response: {:?}", resp); - let req = PerformOpReq::DeviceEarlyBootEnded(kmr_wire::EarlyBootEndedRequest{}); + let req = PerformOpReq::DeviceEarlyBootEnded(kmr_wire::EarlyBootEndedRequest {}); let resp = ta.process_req(req); debug!("DeviceEarlyBootEnded response: {:?}", resp); let clock = clock::StdClock; let current_time = clock.now().0; - let req = PerformOpReq::DeviceGenerateKey(GenerateKeyRequest{ + let req = PerformOpReq::DeviceGenerateKey(GenerateKeyRequest { key_params: vec![ - KeyParam::ApplicationId("com.example.app".as_bytes().to_vec()), - KeyParam::AttestationApplicationId("com.example.app".as_bytes().to_vec()), - KeyParam::Purpose(KeyPurpose::AttestKey), - KeyParam::KeySize(KeySizeInBits(256)), - KeyParam::Algorithm(kmr_wire::keymint::Algorithm::Ec), - KeyParam::EcCurve(kmr_wire::keymint::EcCurve::P256), - KeyParam::Digest(kmr_wire::keymint::Digest::Sha256), - KeyParam::NoAuthRequired, - KeyParam::CertificateNotBefore(DateTime{ms_since_epoch: current_time - 10000}), // -10 seconds - KeyParam::CertificateNotAfter(DateTime{ms_since_epoch: current_time + 31536000000}), // +1 year - KeyParam::CertificateSerial(b"1234567890".to_vec()), - KeyParam::CertificateSubject(kmr_wire::keymint::DEFAULT_CERT_SUBJECT.to_vec()), - KeyParam::AttestationChallenge(b"Test Attestation Challenge".to_vec()), - - ], + KeyParam::ApplicationId("com.example.app".as_bytes().to_vec()), + KeyParam::AttestationApplicationId("com.example.app".as_bytes().to_vec()), + KeyParam::Purpose(KeyPurpose::AttestKey), + KeyParam::KeySize(KeySizeInBits(256)), + KeyParam::Algorithm(kmr_wire::keymint::Algorithm::Ec), + KeyParam::EcCurve(kmr_wire::keymint::EcCurve::P256), + KeyParam::Digest(kmr_wire::keymint::Digest::Sha256), + KeyParam::NoAuthRequired, + KeyParam::CertificateNotBefore(DateTime { + ms_since_epoch: current_time - 10000, + }), // -10 seconds + KeyParam::CertificateNotAfter(DateTime { + ms_since_epoch: current_time + 31536000000, + }), // +1 year + KeyParam::CertificateSerial(b"1234567890".to_vec()), + KeyParam::CertificateSubject(kmr_wire::keymint::DEFAULT_CERT_SUBJECT.to_vec()), + KeyParam::AttestationChallenge(b"Test Attestation Challenge".to_vec()), + ], attestation_key: None, }); let resp = ta.process_req(req); @@ -174,14 +204,78 @@ fn main(){ Some(rsp) => { if let kmr_wire::PerformOpRsp::DeviceGenerateKey(ref key_rsp) = rsp { std::fs::create_dir_all("./omk/output").unwrap(); - std::fs::write("./omk/output/cert.der", key_rsp.ret.certificate_chain[0].encoded_certificate.clone()).unwrap(); + std::fs::write( + "./omk/output/cert.der", + key_rsp.ret.certificate_chain[0].encoded_certificate.clone(), + ) + .unwrap(); } else { error!("Unexpected response: {:?}", resp); } - }, + } None => { error!("No response received"); } } -} \ No newline at end of file + let mut key_descriptor = KeyDescriptor::KeyDescriptor::default(); + + let mut bytes: Vec = Vec::new(); + std::fs::read("./parcel.bin") + .unwrap() + .as_slice() + .read_to_end(&mut bytes) + .unwrap(); + let mut parcel = rsbinder::Parcel::from_vec(bytes); + key_descriptor.read_from_parcel(&mut parcel).unwrap(); + + info!("KeyDescriptor from parcel: {:?}", key_descriptor); + + let mut parcel = rsbinder::Parcel::new(); + + let op = KeystoreOperation {}; + let op = IKeystoreOperation::BnKeystoreOperation::new_binder(op); + + let mut create_op_resp = CreateOperationResponse { + iOperation: Some(op), + operationChallenge: None, + parameters: None, + upgradedBlob: None, + }; + + parcel.write(&mut create_op_resp).unwrap(); + + info!("Parcel raw data: {:x?}", parcel.data()); + info!( + "CreateOperationResponse written to parcel, size: {}", + parcel.data_size() + ); + info!("data: {:?}", parcel); +} + +#[derive(Clone)] +pub struct KeystoreOperation; + +impl rsbinder::Interface for KeystoreOperation {} + +impl IKeystoreOperation::IKeystoreOperation for KeystoreOperation { + fn r#updateAad(&self, _arg_aad_input: &[u8]) -> rsbinder::status::Result<()> { + Ok(()) + } + + fn r#update(&self, _arg_input: &[u8]) -> rsbinder::status::Result>> { + Ok(Some(vec![])) + } + + fn r#finish( + &self, + _arg_input: Option<&[u8]>, + _arg_signature: Option<&[u8]>, + ) -> rsbinder::status::Result>> { + Ok(Some(vec![])) + } + + fn r#abort(&self) -> rsbinder::status::Result<()> { + Ok(()) + } +} diff --git a/libs/rust/src/plat/mod.rs b/libs/rust/src/plat/mod.rs new file mode 100644 index 0000000..86d4de3 --- /dev/null +++ b/libs/rust/src/plat/mod.rs @@ -0,0 +1,2 @@ +pub mod property_watcher; +pub mod utils; diff --git a/libs/rust/src/plat/property_watcher.rs b/libs/rust/src/plat/property_watcher.rs new file mode 100644 index 0000000..b7e380f --- /dev/null +++ b/libs/rust/src/plat/property_watcher.rs @@ -0,0 +1,89 @@ +use log::info; +use rsproperties::PropertyConfig; +use rsproperties_service; + +use anyhow::{anyhow, Context, Ok, Result}; + +#[cfg(target_os = "linux")] +static HAS_INIT: std::sync::Once = std::sync::Once::new(); + +pub struct PropertyWatcher { + name: String, +} + +impl PropertyWatcher { + #[cfg(target_os = "linux")] + pub fn new(name: &str) -> anyhow::Result { + HAS_INIT.call_once(|| { + init(); + }); + Ok(PropertyWatcher { + name: name.to_string(), + }) + } + + #[cfg(not(target_os = "linux"))] + pub fn new(name: &str) -> anyhow::Result { + Ok(PropertyWatcher { name }) + } + + pub fn read(&self) -> Result { + rsproperties::system_properties() + .get_with_result(&self.name.as_str()) + .context(anyhow!("Property '{}' not found", self.name)) + } + + pub fn read_and_parse(&self, mut f: F) -> Result + where + F: FnMut(&str) -> Result, + { + rsproperties::system_properties() + .get_with_result(&self.name.as_str()) + .context(anyhow!("Property '{}' not found", self.name)) + .and_then(|value| f(value.as_str())) + } + + pub fn wait(&self, _old_value: Option<&str>) -> Result<()> { + let system_props = rsproperties::system_properties(); + let val = system_props.find(&self.name)?; + if let Some(val) = val { + system_props.wait(Some(&val), None); + Ok(()) + } else { + Err(anyhow!("Property '{}' not found", self.name)) + } + } +} + +#[tokio::main] +async fn init() -> Result<()> { + std::fs::create_dir_all("./omk/properties").unwrap(); + std::fs::create_dir_all("./omk/property_socket").unwrap(); + // Configure the service + let config = PropertyConfig { + properties_dir: Some("./omk/properties".into()), + socket_dir: Some("./omk/property_socket".into()), + }; + + rsproperties::init(config.clone()); + + // Optional: Load property contexts and build.prop files + let property_contexts: Vec = vec![]; + + let build_props: Vec = vec!["device.prop".into()]; + + // Start the property service + let srv = rsproperties_service::run(config, property_contexts, build_props).await; + + if let Err(e) = srv { + panic!("Property service failed to start, {}", e); + } + + info!("Property service running..."); + + // Keep running until interrupted + tokio::signal::ctrl_c().await?; + info!("Property service shutting down..."); + + Ok(()) +} diff --git a/libs/rust/src/plat/utils.rs b/libs/rust/src/plat/utils.rs new file mode 100644 index 0000000..6bf3c99 --- /dev/null +++ b/libs/rust/src/plat/utils.rs @@ -0,0 +1,71 @@ +use std::sync::Arc; + +use anyhow::Ok; +use log::debug; +use rsbinder::{hub, DeathRecipient}; + +use crate::android::content::pm::IPackageManager::IPackageManager; + +use std::cell::RefCell; + +thread_local! { + static PM: RefCell>> = RefCell::new(None); +} + +struct MyDeathRecipient; + +impl rsbinder::DeathRecipient for MyDeathRecipient { + fn binder_died(&self, _who: &rsbinder::WIBinder) { + PM.with(|p| { + *p.borrow_mut() = None; + }); + debug!("PackageManager died, cleared PM instance"); + } +} + +#[allow(non_snake_case)] +fn get_pm() -> anyhow::Result> { + PM.with(|p| { + if let Some(iPm) = p.borrow().as_ref() { + Ok(iPm.clone()) + } else { + let pm: rsbinder::Strong = hub::get_interface("package")?; + let recipient = Arc::new(MyDeathRecipient {}); + + pm.as_binder() + .link_to_death(Arc::downgrade(&(recipient as Arc)))?; + + *p.borrow_mut() = Some(pm.clone()); + Ok(pm) + } + }) +} + +pub fn get_aaid(uid: u32) -> anyhow::Result { + if (uid == 0) || (uid == 1000) { + return Ok("android".to_string()); + } // system or root + let pm = get_pm()?; + let package_names = pm.getPackagesForUid(uid as i32)?; + + debug!("get_aaid: package_name = {:?}", package_names); + + Ok(package_names[0].clone()) +} + +pub const AID_USER_OFFSET: u32 = 100000; + +/// Gets the user id from a uid. +pub fn multiuser_get_user_id(uid: u32) -> u32 { + uid / AID_USER_OFFSET +} + +/// Gets the app id from a uid. +pub fn multiuser_get_app_id(uid: u32) -> u32 { + uid % AID_USER_OFFSET +} + +/// Extracts the android user from the given uid. +pub fn uid_to_android_user(uid: u32) -> u32 { + multiuser_get_user_id(uid) +} diff --git a/libs/rust/src/proto/mod.rs b/libs/rust/src/proto/mod.rs index e626ba5..30f61eb 100644 --- a/libs/rust/src/proto/mod.rs +++ b/libs/rust/src/proto/mod.rs @@ -1 +1 @@ -pub mod storage; \ No newline at end of file +pub mod storage; diff --git a/libs/rust/src/utils.rs b/libs/rust/src/utils.rs index 05e6a12..f277ca7 100644 --- a/libs/rust/src/utils.rs +++ b/libs/rust/src/utils.rs @@ -2,9 +2,26 @@ /// by invoking the system call since Rust does not support getting monotonic time instance /// as an integer. pub fn get_current_time_in_milliseconds() -> i64 { - let mut current_time = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let mut current_time = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; // SAFETY: The pointer is valid because it comes from a reference, and clock_gettime doesn't // retain it beyond the call. unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut current_time) }; current_time.tv_sec as i64 * 1000 + (current_time.tv_nsec as i64 / 1_000_000) -} \ No newline at end of file +} + +pub trait ParcelExt { + fn data(&self) -> &[u8]; +} + +impl ParcelExt for rsbinder::Parcel { + fn data(&self) -> &[u8] { + unsafe { + let data = self.as_ptr(); + let parcel_size = self.data_size(); + std::slice::from_raw_parts(data, parcel_size) + } + } +} diff --git a/libs/rust/src/watchdog.rs b/libs/rust/src/watchdog.rs new file mode 100644 index 0000000..40b932c --- /dev/null +++ b/libs/rust/src/watchdog.rs @@ -0,0 +1,48 @@ +// Copyright 2023, 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. + +//! Helpers for the watchdog module. + +use std::sync::{Arc, LazyLock}; +use std::time::Duration; +pub use watchdog_rs::WatchPoint; +use watchdog_rs::Watchdog; + +/// Default timeout interval, in milliseconds. +pub const DEFAULT_TIMEOUT_MS: u64 = 500; + +const DEFAULT_TIMEOUT: Duration = Duration::from_millis(DEFAULT_TIMEOUT_MS); + +/// A Watchdog thread, that can be used to create watch points. +static WD: LazyLock> = LazyLock::new(|| Watchdog::new(Duration::from_secs(10))); + +/// Sets a watch point with `id` and a timeout of `millis` milliseconds. +pub fn watch_millis(id: &'static str, millis: u64) -> Option { + Watchdog::watch(&WD, id, Duration::from_millis(millis)) +} + +/// Sets a watch point with `id` and a default timeout of [`DEFAULT_TIMEOUT_MS`] milliseconds. +pub fn watch(id: &'static str) -> Option { + Watchdog::watch(&WD, id, DEFAULT_TIMEOUT) +} + +/// Like `watch_millis` but with context that is included every time a report is printed about +/// this watch point. +pub fn watch_millis_with( + id: &'static str, + millis: u64, + context: impl std::fmt::Debug + Send + 'static, +) -> Option { + Watchdog::watch_with(&WD, id, Duration::from_millis(millis), context) +} diff --git a/libs/rust/ta/src/cert.rs b/libs/rust/ta/src/cert.rs index 2956108..52e5efb 100644 --- a/libs/rust/ta/src/cert.rs +++ b/libs/rust/ta/src/cert.rs @@ -16,7 +16,6 @@ use crate::keys::SigningInfo; use core::time::Duration; -use std::borrow::Cow; use der::asn1::{BitString, OctetString, OctetStringRef, SetOfVec}; use der::{ asn1::{GeneralizedTime, Null, UtcTime}, @@ -39,6 +38,7 @@ use kmr_wire::{ KeySizeInBits, RsaExponent, }; use spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfoOwned}; +use std::borrow::Cow; use x509_cert::serial_number::SerialNumber; use x509_cert::{ certificate::{Certificate, TbsCertificate, Version}, @@ -88,7 +88,10 @@ pub(crate) fn tbs_certificate<'a>( KeyMaterial::Rsa(_) => crypto::rsa::SHA256_PKCS1_SIGNATURE_OID, KeyMaterial::Ec(curve, _, _) => crypto::ec::curve_to_signing_oid(curve), _ => { - return Err(km_err!(UnsupportedAlgorithm, "unexpected cert signing key type")); + return Err(km_err!( + UnsupportedAlgorithm, + "unexpected cert signing key type" + )); } }, None => { @@ -148,7 +151,10 @@ pub(crate) fn tbs_certificate<'a>( version: Version::V3, serial_number: SerialNumber::new(cert_serial) .map_err(|e| der_err!(e, "failed to build serial number for {:?}", cert_serial))?, - signature: AlgorithmIdentifier { oid: sig_alg_oid, parameters: None }, + signature: AlgorithmIdentifier { + oid: sig_alg_oid, + parameters: None, + }, issuer: RdnSequence::from_der(cert_issuer) .map_err(|e| der_err!(e, "failed to build issuer"))?, validity: x509_cert::time::Validity { @@ -244,7 +250,10 @@ pub(crate) fn key_usage_extension_bits(params: &[KeyParam]) -> KeyUsage { /// Build basic constraints extension value pub(crate) fn basic_constraints_ext_value(ca_required: bool) -> BasicConstraints { - BasicConstraints { ca: ca_required, path_len_constraint: None } + BasicConstraints { + ca: ca_required, + path_len_constraint: None, + } } /// Attestation extension contents @@ -346,8 +355,13 @@ pub(crate) fn attestation_extension<'a>( None, &[], )?; - let sec_level = SecurityLevel::try_from(security_level as u32) - .map_err(|_| km_err!(InvalidArgument, "invalid security level {:?}", security_level))?; + let sec_level = SecurityLevel::try_from(security_level as u32).map_err(|_| { + km_err!( + InvalidArgument, + "invalid security level {:?}", + security_level + ) + })?; let ext = AttestationExtension { attestation_version: keymint_version, attestation_security_level: sec_level, @@ -453,7 +467,11 @@ impl<'a> AuthorizationList<'a> { app_id: Option<&'a [u8]>, additional_attestation_info: &'a [KeyParam], ) -> Result { - check_attestation_id!(keygen_params, AttestationIdBrand, attestation_ids.map(|v| &v.brand)); + check_attestation_id!( + keygen_params, + AttestationIdBrand, + attestation_ids.map(|v| &v.brand) + ); check_attestation_id!( keygen_params, AttestationIdDevice, @@ -469,22 +487,37 @@ impl<'a> AuthorizationList<'a> { AttestationIdSerial, attestation_ids.map(|v| &v.serial) ); - check_attestation_id!(keygen_params, AttestationIdImei, attestation_ids.map(|v| &v.imei)); + check_attestation_id!( + keygen_params, + AttestationIdImei, + attestation_ids.map(|v| &v.imei) + ); check_attestation_id!( keygen_params, AttestationIdSecondImei, attestation_ids.map(|v| &v.imei2) ); - check_attestation_id!(keygen_params, AttestationIdMeid, attestation_ids.map(|v| &v.meid)); + check_attestation_id!( + keygen_params, + AttestationIdMeid, + attestation_ids.map(|v| &v.meid) + ); check_attestation_id!( keygen_params, AttestationIdManufacturer, attestation_ids.map(|v| &v.manufacturer) ); - check_attestation_id!(keygen_params, AttestationIdModel, attestation_ids.map(|v| &v.model)); + check_attestation_id!( + keygen_params, + AttestationIdModel, + attestation_ids.map(|v| &v.model) + ); let encoded_rot = if let Some(rot) = rot_info { - Some(rot.to_der().map_err(|e| der_err!(e, "failed to encode RoT"))?) + Some( + rot.to_der() + .map_err(|e| der_err!(e, "failed to encode RoT"))?, + ) } else { None }; @@ -531,9 +564,9 @@ impl<'a> AuthorizationList<'a> { | KeyParam::AttestationIdModel(_) => { keygen_params.try_push(param).map_err(der_alloc_err)? } - KeyParam::ModuleHash(_) => { - additional_attestation_info.try_push(param).map_err(der_alloc_err)? - } + KeyParam::ModuleHash(_) => additional_attestation_info + .try_push(param) + .map_err(der_alloc_err)?, _ => auths.try_push(param).map_err(der_alloc_err)?, } } @@ -566,8 +599,11 @@ fn decode_opt_field<'a, R: der::Reader<'a>>( key_params: &mut Vec, ) -> Result, der::Error> { // Decode the tag if no tag is provided - let tag = - if already_read_tag.is_none() { decode_tag_from_bytes(decoder)? } else { already_read_tag }; + let tag = if already_read_tag.is_none() { + decode_tag_from_bytes(decoder)? + } else { + already_read_tag + }; match tag { Some(tag) if tag == expected_tag => { // Decode the length of the inner encoding @@ -848,7 +884,9 @@ fn decode_value_from_bytes( } Tag::RootOfTrust => { key_params - .try_push(KeyParam::RootOfTrust(try_to_vec(tlv_bytes).map_err(der_alloc_err)?)) + .try_push(KeyParam::RootOfTrust( + try_to_vec(tlv_bytes).map_err(der_alloc_err)?, + )) .map_err(der_alloc_err)?; } Tag::OsVersion => { @@ -1096,7 +1134,10 @@ impl EncodeValue for AuthorizationList<'_> { + asn1_len(asn1_null!(self.auths, RollbackResistance))? + asn1_len(asn1_null!(self.auths, EarlyBootOnly))? + asn1_len(asn1_integer_datetime!(self.auths, ActiveDatetime))? - + asn1_len(asn1_integer_datetime!(self.auths, OriginationExpireDatetime))? + + asn1_len(asn1_integer_datetime!( + self.auths, + OriginationExpireDatetime + ))? + asn1_len(asn1_integer_datetime!(self.auths, UsageExpireDatetime))? + asn1_len(asn1_integer!(self.auths, UsageCountLimit))? + asn1_len(asn1_null!(self.auths, NoAuthRequired))? @@ -1130,17 +1171,29 @@ impl EncodeValue for AuthorizationList<'_> { length = length + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdBrand))? + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdDevice))? - + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdProduct))? + + asn1_len(asn1_octet_string!( + &self.keygen_params, + AttestationIdProduct + ))? + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdSerial))? + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdImei))? + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdMeid))? - + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdManufacturer))? + + asn1_len(asn1_octet_string!( + &self.keygen_params, + AttestationIdManufacturer + ))? + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdModel))? + asn1_len(asn1_integer!(self.auths, VendorPatchlevel))? + asn1_len(asn1_integer!(self.auths, BootPatchlevel))? + asn1_len(asn1_null!(self.auths, DeviceUniqueAttestation))? - + asn1_len(asn1_octet_string!(&self.keygen_params, AttestationIdSecondImei))? - + asn1_len(asn1_octet_string!(&self.additional_attestation_info, ModuleHash))?; + + asn1_len(asn1_octet_string!( + &self.keygen_params, + AttestationIdSecondImei + ))? + + asn1_len(asn1_octet_string!( + &self.additional_attestation_info, + ModuleHash + ))?; length } @@ -1159,8 +1212,14 @@ impl EncodeValue for AuthorizationList<'_> { asn1_val(asn1_null!(self.auths, RollbackResistance), writer)?; asn1_val(asn1_null!(self.auths, EarlyBootOnly), writer)?; asn1_val(asn1_integer_datetime!(self.auths, ActiveDatetime), writer)?; - asn1_val(asn1_integer_datetime!(self.auths, OriginationExpireDatetime), writer)?; - asn1_val(asn1_integer_datetime!(self.auths, UsageExpireDatetime), writer)?; + asn1_val( + asn1_integer_datetime!(self.auths, OriginationExpireDatetime), + writer, + )?; + asn1_val( + asn1_integer_datetime!(self.auths, UsageExpireDatetime), + writer, + )?; asn1_val(asn1_integer!(self.auths, UsageCountLimit), writer)?; // Skip `UserSecureId` as it's only included in the extension for // importWrappedKey() cases. @@ -1192,19 +1251,49 @@ impl EncodeValue for AuthorizationList<'_> { .encode(writer)?; } // Accuracy of attestation IDs has already been checked, so just copy across. - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdBrand), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdDevice), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdProduct), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdSerial), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdImei), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdMeid), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdManufacturer), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdModel), writer)?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdBrand), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdDevice), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdProduct), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdSerial), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdImei), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdMeid), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdManufacturer), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdModel), + writer, + )?; asn1_val(asn1_integer!(self.auths, VendorPatchlevel), writer)?; asn1_val(asn1_integer!(self.auths, BootPatchlevel), writer)?; asn1_val(asn1_null!(self.auths, DeviceUniqueAttestation), writer)?; - asn1_val(asn1_octet_string!(&self.keygen_params, AttestationIdSecondImei), writer)?; - asn1_val(asn1_octet_string!(&self.additional_attestation_info, ModuleHash), writer)?; + asn1_val( + asn1_octet_string!(&self.keygen_params, AttestationIdSecondImei), + writer, + )?; + asn1_val( + asn1_octet_string!(&self.additional_attestation_info, ModuleHash), + writer, + )?; Ok(()) } } @@ -1458,7 +1547,10 @@ mod tests { // encode assert_eq!(hex::encode(&got), want); // decode from encoded - assert_eq!(AuthorizationList::from_der(got.as_slice()).unwrap(), authz_list); + assert_eq!( + AuthorizationList::from_der(got.as_slice()).unwrap(), + authz_list + ); } #[test] @@ -1538,7 +1630,10 @@ mod tests { let got = AuthorizationList::from_der(&input).unwrap(); let want = AuthorizationList::new( - &[KeyParam::Algorithm(keymint::Algorithm::Ec), KeyParam::UserSecureId(2)], + &[ + KeyParam::Algorithm(keymint::Algorithm::Ec), + KeyParam::UserSecureId(2), + ], &[], None, Some(RootOfTrust { @@ -1584,7 +1679,10 @@ mod tests { fn test_authz_list_order_fail() { use kmr_wire::keymint::Digest; let authz_list = AuthorizationList::new( - &[KeyParam::Digest(Digest::Sha1), KeyParam::Digest(Digest::None)], + &[ + KeyParam::Digest(Digest::Sha1), + KeyParam::Digest(Digest::None), + ], &[], None, Some(RootOfTrust { diff --git a/libs/rust/ta/src/clock.rs b/libs/rust/ta/src/clock.rs index f0da98c..906be9b 100644 --- a/libs/rust/ta/src/clock.rs +++ b/libs/rust/ta/src/clock.rs @@ -14,16 +14,19 @@ //! TA functionality for secure clocks. -use Vec; use core::mem::size_of; use kmr_common::{km_err, vec_try_with_capacity, Error}; use kmr_wire::secureclock::{TimeStampToken, TIME_STAMP_MAC_LABEL}; +use Vec; impl crate::KeyMintTa { pub(crate) fn generate_timestamp(&self, challenge: i64) -> Result { if let Some(clock) = &self.imp.clock { - let mut ret = - TimeStampToken { challenge, timestamp: clock.now().into(), mac: Vec::new() }; + let mut ret = TimeStampToken { + challenge, + timestamp: clock.now().into(), + mac: Vec::new(), + }; let mac_input = self.dev.keys.timestamp_token_mac_input(&ret)?; ret.mac = self.device_hmac(&mac_input)?; Ok(ret) diff --git a/libs/rust/ta/src/device.rs b/libs/rust/ta/src/device.rs index 4b55b11..a6efa18 100644 --- a/libs/rust/ta/src/device.rs +++ b/libs/rust/ta/src/device.rs @@ -91,7 +91,8 @@ pub trait RetrieveKeyMaterial { fn unique_id_hbk(&self, ckdf: &dyn crypto::Ckdf) -> Result { // By default, use CKDF on the key agreement secret to derive a key. let unique_id_label = b"UniqueID HBK 32B"; - ckdf.ckdf(&self.kak()?, unique_id_label, &[], 32).map(crypto::hmac::Key::new) + ckdf.ckdf(&self.kak()?, unique_id_label, &[], 32) + .map(crypto::hmac::Key::new) } /// Build the HMAC input for a [`TimeStampToken`]. The default implementation produces diff --git a/libs/rust/ta/src/keys.rs b/libs/rust/ta/src/keys.rs index 9971a49..5a50bbe 100644 --- a/libs/rust/ta/src/keys.rs +++ b/libs/rust/ta/src/keys.rs @@ -15,9 +15,7 @@ //! TA functionality related to key generation/import/upgrade. use crate::{cert, device, AttestationChainInfo}; -use Vec; use core::{borrow::Borrow, cmp::Ordering, convert::TryFrom}; -use std::collections::btree_map::Entry; use der::{referenced::RefToOwned, Decode, Sequence}; use kmr_common::{ crypto::{self, aes, rsa, KeyMaterial, OpaqueOr}, @@ -34,7 +32,9 @@ use kmr_wire::{ }; use log::{error, warn}; use spki::SubjectPublicKeyInfoOwned; +use std::collections::btree_map::Entry; use x509_cert::ext::pkix::KeyUsages; +use Vec; /// Maximum size of an attestation challenge value. const MAX_ATTESTATION_CHALLENGE_LEN: usize = 128; @@ -112,9 +112,12 @@ impl crate::KeyMintTa { pub(crate) fn get_signing_info( &self, key_type: device::SigningKeyType, - ) -> Result { + ) -> Result, Error> { let sign_info = self.dev.sign_info.as_ref().ok_or_else(|| { - km_err!(AttestationKeysNotProvisioned, "batch attestation keys not available") + km_err!( + AttestationKeysNotProvisioned, + "batch attestation keys not available" + ) })?; // Retrieve the chain and issuer information, which is cached after first retrieval. let mut attestation_chain_info = self.attestation_chain_info.borrow_mut(); @@ -156,41 +159,51 @@ impl crate::KeyMintTa { // Build and encode basic constraints extension value, based on the key usage extension // value - let basic_constraints_ext_val = - if (key_usage_ext_bits.0 & KeyUsages::KeyCertSign).bits().count_ones() != 0 { - let basic_constraints = cert::basic_constraints_ext_value(true); - Some(cert::asn1_der_encode(&basic_constraints).map_err(|e| { - der_err!(e, "failed to encode basic constraints {:?}", basic_constraints) - })?) - } else { - None - }; + let basic_constraints_ext_val = if (key_usage_ext_bits.0 & KeyUsages::KeyCertSign) + .bits() + .count_ones() + != 0 + { + let basic_constraints = cert::basic_constraints_ext_value(true); + Some(cert::asn1_der_encode(&basic_constraints).map_err(|e| { + der_err!( + e, + "failed to encode basic constraints {:?}", + basic_constraints + ) + })?) + } else { + None + }; // Build and encode attestation extension if present let id_info = self.get_attestation_ids(); - let attest_ext_val = - if let Some(SigningInfo { attestation_info: Some((challenge, app_id)), .. }) = &info { - let unique_id = self.calculate_unique_id(app_id, params)?; - let boot_info = self.boot_info_hashed_key()?; - let attest_ext = cert::attestation_extension( - self.aidl_version as i32, - challenge, - app_id, - self.hw_info.security_level, - id_info.as_ref().map(|v| v.borrow()), - params, - chars, - &unique_id, - &boot_info, - &self.additional_attestation_info, - )?; - Some( - cert::asn1_der_encode(&attest_ext) - .map_err(|e| der_err!(e, "failed to encode attestation extension"))?, - ) - } else { - None - }; + let attest_ext_val = if let Some(SigningInfo { + attestation_info: Some((challenge, app_id)), + .. + }) = &info + { + let unique_id = self.calculate_unique_id(app_id, params)?; + let boot_info = self.boot_info_hashed_key()?; + let attest_ext = cert::attestation_extension( + self.aidl_version as i32, + challenge, + app_id, + self.hw_info.security_level, + id_info.as_ref().map(|v| v.borrow()), + params, + chars, + &unique_id, + &boot_info, + &self.additional_attestation_info, + )?; + Some( + cert::asn1_der_encode(&attest_ext) + .map_err(|e| der_err!(e, "failed to encode attestation extension"))?, + ) + } else { + None + }; let tbs_cert = cert::tbs_certificate( &info, @@ -212,7 +225,9 @@ impl crate::KeyMintTa { let cert = cert::certificate(tbs_cert, &sig_data)?; let cert_data = cert::asn1_der_encode(&cert) .map_err(|e| der_err!(e, "failed to encode certificate"))?; - Ok(keymint::Certificate { encoded_certificate: cert_data }) + Ok(keymint::Certificate { + encoded_certificate: cert_data, + }) } /// Perform a complete signing operation using default modes. @@ -237,7 +252,10 @@ impl crate::KeyMintTa { op.update(tbs_data)?; op.finish() } - _ => Err(km_err!(IncompatibleAlgorithm, "unexpected cert signing key type")), + _ => Err(km_err!( + IncompatibleAlgorithm, + "unexpected cert signing key type" + )), } } @@ -291,26 +309,36 @@ impl crate::KeyMintTa { self.add_keymint_tags(&mut chars, KeyOrigin::Generated)?; let key_material = match keygen_info { crypto::KeyGenInfo::Aes(variant) => { - self.imp.aes.generate_key(&mut *self.imp.rng, variant, params)? + self.imp + .aes + .generate_key(&mut *self.imp.rng, variant, params)? } crypto::KeyGenInfo::TripleDes => { self.imp.des.generate_key(&mut *self.imp.rng, params)? } crypto::KeyGenInfo::Hmac(key_size) => { - self.imp.hmac.generate_key(&mut *self.imp.rng, key_size, params)? + self.imp + .hmac + .generate_key(&mut *self.imp.rng, key_size, params)? } crypto::KeyGenInfo::Rsa(key_size, pub_exponent) => { - self.imp.rsa.generate_key(&mut *self.imp.rng, key_size, pub_exponent, params)? + self.imp + .rsa + .generate_key(&mut *self.imp.rng, key_size, pub_exponent, params)? } crypto::KeyGenInfo::NistEc(curve) => { - self.imp.ec.generate_nist_key(&mut *self.imp.rng, curve, params)? - } - crypto::KeyGenInfo::Ed25519 => { - self.imp.ec.generate_ed25519_key(&mut *self.imp.rng, params)? - } - crypto::KeyGenInfo::X25519 => { - self.imp.ec.generate_x25519_key(&mut *self.imp.rng, params)? + self.imp + .ec + .generate_nist_key(&mut *self.imp.rng, curve, params)? } + crypto::KeyGenInfo::Ed25519 => self + .imp + .ec + .generate_ed25519_key(&mut *self.imp.rng, params)?, + crypto::KeyGenInfo::X25519 => self + .imp + .ec + .generate_x25519_key(&mut *self.imp.rng, params)?, }; Ok((key_material, chars)) } @@ -324,7 +352,10 @@ impl crate::KeyMintTa { import_type: KeyImport, ) -> Result { if !self.in_early_boot && get_bool_tag_value!(params, EarlyBootOnly)? { - return Err(km_err!(EarlyBootEnded, "attempt to use EARLY_BOOT key after early boot")); + return Err(km_err!( + EarlyBootEnded, + "attempt to use EARLY_BOOT key after early boot" + )); } let (mut chars, key_material) = tag::extract_key_import_characteristics( @@ -443,8 +474,10 @@ impl crate::KeyMintTa { _ => return Err(km_err!(InvalidArgument, "unexpected key type!")), }; - let mut info = self - .get_signing_info(device::SigningKeyType { which: which_key, algo_hint })?; + let mut info = self.get_signing_info(device::SigningKeyType { + which: which_key, + algo_hint, + })?; info.attestation_info = attestation_info; Some(info) } @@ -529,14 +562,20 @@ impl crate::KeyMintTa { ) -> Result { // Decrypt the wrapping key blob let (wrapping_key, _) = self.keyblob_parse_decrypt(wrapping_key_blob, unwrapping_params)?; - let keyblob::PlaintextKeyBlob { characteristics, key_material } = wrapping_key; + let keyblob::PlaintextKeyBlob { + characteristics, + key_material, + } = wrapping_key; // Decode the ASN.1 DER encoded `SecureKeyWrapper`. let mut secure_key_wrapper = SecureKeyWrapper::from_der(wrapped_key_data) .map_err(|e| der_err!(e, "failed to parse SecureKeyWrapper"))?; if secure_key_wrapper.version != SECURE_KEY_WRAPPER_VERSION { - return Err(km_err!(InvalidArgument, "invalid version in Secure Key Wrapper.")); + return Err(km_err!( + InvalidArgument, + "invalid version in Secure Key Wrapper." + )); } // Decrypt the masked transport key, using an RSA key. (Only RSA wrapping keys are supported @@ -551,11 +590,16 @@ impl crate::KeyMintTa { // Decrypt the masked and encrypted transport key let mut crypto_op = self.imp.rsa.begin_decrypt(key, decrypt_mode)?; - crypto_op.as_mut().update(secure_key_wrapper.encrypted_transport_key)?; + crypto_op + .as_mut() + .update(secure_key_wrapper.encrypted_transport_key)?; crypto_op.finish()? } _ => { - return Err(km_err!(InvalidArgument, "invalid key algorithm for transport key")); + return Err(km_err!( + InvalidArgument, + "invalid key algorithm for transport key" + )); } }; @@ -568,8 +612,11 @@ impl crate::KeyMintTa { )); } - let unmasked_transport_key: Vec = - masked_transport_key.iter().zip(masking_key).map(|(x, y)| x ^ y).collect(); + let unmasked_transport_key: Vec = masked_transport_key + .iter() + .zip(masking_key) + .map(|(x, y)| x ^ y) + .collect(); let aes_transport_key = aes::Key::Aes256(unmasked_transport_key.try_into().map_err(|_e| { diff --git a/libs/rust/ta/src/lib.rs b/libs/rust/ta/src/lib.rs index 357e0a1..44b16b2 100644 --- a/libs/rust/ta/src/lib.rs +++ b/libs/rust/ta/src/lib.rs @@ -14,12 +14,9 @@ //! KeyMint trusted application (TA) implementation. - use core::cmp::Ordering; use core::mem::size_of; use core::{cell::RefCell, convert::TryFrom}; -use std::collections::BTreeMap; -use std::rc::Rc; use device::DiceInfo; use kmr_common::{ crypto::{self, hmac, OpaqueOr}, @@ -40,6 +37,8 @@ use kmr_wire::{ *, }; use log::{debug, error, info, trace, warn}; +use std::collections::BTreeMap; +use std::rc::Rc; mod cert; mod clock; @@ -265,6 +264,7 @@ pub struct RpcInfoV3 { /// Enum to distinguish the set of information required for different versions of IRPC HAL /// implementations +#[derive(Debug)] pub enum RpcInfo { /// Information for v2 of the IRPC HAL. V2(RpcInfoV2), @@ -465,14 +465,20 @@ impl KeyMintTa { check(v, hal_info.os_version, "OS version")?; } } else { - error!("OS version not available, can't check for upgrade from {}", v); + error!( + "OS version not available, can't check for upgrade from {}", + v + ); } } KeyParam::OsPatchlevel(v) => { if let Some(hal_info) = &self.hal_info { check(v, hal_info.os_patchlevel, "OS patchlevel")?; } else { - error!("OS patchlevel not available, can't check for upgrade from {}", v); + error!( + "OS patchlevel not available, can't check for upgrade from {}", + v + ); } } KeyParam::VendorPatchlevel(v) => { @@ -489,7 +495,10 @@ impl KeyMintTa { if let Some(boot_info) = &self.boot_info { check(v, boot_info.boot_patchlevel, "boot patchlevel")?; } else { - error!("boot patchlevel not available, can't check for upgrade from {}", v); + error!( + "boot patchlevel not available, can't check for upgrade from {}", + v + ); } } _ => {} @@ -500,13 +509,18 @@ impl KeyMintTa { /// Generate a unique identifier for a keyblob. fn key_id(&self, keyblob: &[u8]) -> Result { - let mut hmac_op = - self.imp.hmac.begin(crypto::hmac::Key(vec_try![0; 16]?).into(), Digest::Sha256)?; + let mut hmac_op = self + .imp + .hmac + .begin(crypto::hmac::Key(vec_try![0; 16]?).into(), Digest::Sha256)?; hmac_op.update(keyblob)?; let tag = hmac_op.finish()?; Ok(KeyId(tag.try_into().map_err(|_e| { - km_err!(SecureHwCommunicationFailed, "wrong size output from HMAC-SHA256") + km_err!( + SecureHwCommunicationFailed, + "wrong size output from HMAC-SHA256" + ) })?)) } @@ -518,7 +532,10 @@ impl KeyMintTa { match &self.use_count[idx] { None if free_idx.is_none() => free_idx = Some(idx), None => {} - Some(UseCount { key_id: k, count: _count }) if *k == key_id => { + Some(UseCount { + key_id: k, + count: _count, + }) if *k == key_id => { slot_idx = Some(idx); break; } @@ -536,13 +553,21 @@ impl KeyMintTa { if let Some(idx) = slot_idx { let c = self.use_count[idx].as_mut().unwrap(); // safe: code above guarantees if c.count >= max_uses as u64 { - Err(km_err!(KeyMaxOpsExceeded, "use count {} >= limit {}", c.count, max_uses)) + Err(km_err!( + KeyMaxOpsExceeded, + "use count {} >= limit {}", + c.count, + max_uses + )) } else { c.count += 1; Ok(()) } } else { - Err(km_err!(TooManyOperations, "too many use-counted keys already in play")) + Err(km_err!( + TooManyOperations, + "too many use-counted keys already in play" + )) } } @@ -551,7 +576,10 @@ impl KeyMintTa { /// implementation-specific manner). pub fn set_boot_info(&mut self, boot_info: keymint::BootInfo) -> Result<(), Error> { if !self.in_early_boot { - error!("Rejecting attempt to set boot info {:?} after early boot", boot_info); + error!( + "Rejecting attempt to set boot info {:?} after early boot", + boot_info + ); return Err(km_err!( EarlyBootEnded, "attempt to set boot info to {boot_info:?} after early boot" @@ -614,12 +642,21 @@ impl KeyMintTa { 200 => KeyMintHalVersion::V2, 300 => KeyMintHalVersion::V3, 400 => KeyMintHalVersion::V4, - _ => return Err(km_err!(InvalidArgument, "unsupported HAL version {}", aidl_version)), + _ => { + return Err(km_err!( + InvalidArgument, + "unsupported HAL version {}", + aidl_version + )) + } }; if aidl_version == self.aidl_version { debug!("Set aidl_version to existing version {aidl_version:?}"); } else if cfg!(feature = "downgrade") { - info!("Change aidl_version from {:?} to {:?}", self.aidl_version, aidl_version); + info!( + "Change aidl_version from {:?} to {:?}", + self.aidl_version, aidl_version + ); self.aidl_version = aidl_version; } else { // Only allow HAL-triggered downgrade if the "downgrade" feature is enabled. @@ -732,7 +769,9 @@ impl KeyMintTa { } PerformOpReq::SetAttestationIds(req) => { self.set_attestation_ids(req.ids); - op_ok_rsp(PerformOpRsp::SetAttestationIds(SetAttestationIdsResponse {})) + op_ok_rsp(PerformOpRsp::SetAttestationIds( + SetAttestationIdsResponse {}, + )) } PerformOpReq::SetHalVersion(req) => match self.set_hal_version(req.aidl_version) { Ok(_) => op_ok_rsp(PerformOpRsp::SetHalVersion(SetHalVersionResponse {})), @@ -769,9 +808,9 @@ impl KeyMintTa { // IKeyMintDevice messages. PerformOpReq::DeviceGetHardwareInfo(_req) => match self.get_hardware_info() { - Ok(ret) => { - op_ok_rsp(PerformOpRsp::DeviceGetHardwareInfo(GetHardwareInfoResponse { ret })) - } + Ok(ret) => op_ok_rsp(PerformOpRsp::DeviceGetHardwareInfo( + GetHardwareInfoResponse { ret }, + )), Err(e) => op_error_rsp(GetHardwareInfoRequest::CODE, e), }, PerformOpReq::DeviceAddRngEntropy(req) => match self.add_rng_entropy(&req.data) { @@ -807,11 +846,9 @@ impl KeyMintTa { req.password_sid, req.biometric_sid, ) { - Ok(ret) => { - op_ok_rsp(PerformOpRsp::DeviceImportWrappedKey(ImportWrappedKeyResponse { - ret, - })) - } + Ok(ret) => op_ok_rsp(PerformOpRsp::DeviceImportWrappedKey( + ImportWrappedKeyResponse { ret }, + )), Err(e) => op_error_rsp(ImportWrappedKeyRequest::CODE, e), } } @@ -845,9 +882,9 @@ impl KeyMintTa { } } PerformOpReq::DeviceEarlyBootEnded(_req) => match self.early_boot_ended() { - Ok(_ret) => { - op_ok_rsp(PerformOpRsp::DeviceEarlyBootEnded(EarlyBootEndedResponse {})) - } + Ok(_ret) => op_ok_rsp(PerformOpRsp::DeviceEarlyBootEnded( + EarlyBootEndedResponse {}, + )), Err(e) => op_error_rsp(EarlyBootEndedRequest::CODE, e), }, PerformOpReq::DeviceConvertStorageKeyToEphemeral(req) => { @@ -935,15 +972,18 @@ impl KeyMintTa { // IRemotelyProvisionedComponentOperation messages. PerformOpReq::RpcGetHardwareInfo(_req) => match self.get_rpc_hardware_info() { - Ok(ret) => { - op_ok_rsp(PerformOpRsp::RpcGetHardwareInfo(GetRpcHardwareInfoResponse { ret })) - } + Ok(ret) => op_ok_rsp(PerformOpRsp::RpcGetHardwareInfo( + GetRpcHardwareInfoResponse { ret }, + )), Err(e) => op_error_rsp(GetRpcHardwareInfoRequest::CODE, e), }, PerformOpReq::RpcGenerateEcdsaP256KeyPair(req) => { match self.generate_ecdsa_p256_keypair(rpc::TestMode(req.test_mode)) { Ok((pubkey, ret)) => op_ok_rsp(PerformOpRsp::RpcGenerateEcdsaP256KeyPair( - GenerateEcdsaP256KeyPairResponse { maced_public_key: pubkey, ret }, + GenerateEcdsaP256KeyPairResponse { + maced_public_key: pubkey, + ret, + }, )), Err(e) => op_error_rsp(GenerateEcdsaP256KeyPairRequest::CODE, e), } @@ -957,7 +997,11 @@ impl KeyMintTa { ) { Ok((device_info, protected_data, ret)) => { op_ok_rsp(PerformOpRsp::RpcGenerateCertificateRequest( - GenerateCertificateRequestResponse { device_info, protected_data, ret }, + GenerateCertificateRequestResponse { + device_info, + protected_data, + ret, + }, )) } Err(e) => op_error_rsp(GenerateCertificateRequestRequest::CODE, e), @@ -976,7 +1020,11 @@ impl KeyMintTa { fn add_rng_entropy(&mut self, data: &[u8]) -> Result<(), Error> { if data.len() > 2048 { - return Err(km_err!(InvalidInputLength, "entropy size {} too large", data.len())); + return Err(km_err!( + InvalidInputLength, + "entropy size {} too large", + data.len() + )); }; info!("add {} bytes of entropy", data.len()); @@ -990,7 +1038,7 @@ impl KeyMintTa { Ok(()) } - fn get_hardware_info(&self) -> Result { + pub fn get_hardware_info(&self) -> Result { Ok(KeyMintHardwareInfo { version_number: self.hw_info.version_number, security_level: self.hw_info.security_level, @@ -1008,9 +1056,10 @@ impl KeyMintTa { { // We have to trust that any secure deletion slot in the keyblob is valid, because the // key can't be decrypted. - if let (Some(sdd_mgr), Some(slot)) = - (&mut self.dev.sdd_mgr, encrypted_keyblob.secure_deletion_slot) - { + if let (Some(sdd_mgr), Some(slot)) = ( + &mut self.dev.sdd_mgr, + encrypted_keyblob.secure_deletion_slot, + ) { if let Err(e) = sdd_mgr.delete_secret(slot) { error!("failed to delete secure deletion slot: {:?}", e); } @@ -1047,14 +1096,20 @@ impl KeyMintTa { } None => { error!("destroying device attestation IDs requested but not supported"); - Err(km_err!(Unimplemented, "no attestation ID functionality available")) + Err(km_err!( + Unimplemented, + "no attestation ID functionality available" + )) } } } fn get_root_of_trust_challenge(&mut self) -> Result<[u8; 16], Error> { if !self.is_strongbox() { - return Err(km_err!(Unimplemented, "root-of-trust challenge only for StrongBox")); + return Err(km_err!( + Unimplemented, + "root-of-trust challenge only for StrongBox" + )); } self.imp.rng.fill_bytes(&mut self.rot_challenge[..]); Ok(self.rot_challenge) @@ -1062,7 +1117,10 @@ impl KeyMintTa { fn get_root_of_trust(&mut self, challenge: &[u8]) -> Result, Error> { if self.is_strongbox() { - return Err(km_err!(Unimplemented, "root-of-trust retrieval not for StrongBox")); + return Err(km_err!( + Unimplemented, + "root-of-trust retrieval not for StrongBox" + )); } let payload = self .boot_info_hashed_key()? @@ -1071,7 +1129,9 @@ impl KeyMintTa { let mac0 = coset::CoseMac0Builder::new() .protected( - coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::HMAC_256_256).build(), + coset::HeaderBuilder::new() + .algorithm(coset::iana::Algorithm::HMAC_256_256) + .build(), ) .payload(payload) .try_create_tag(challenge, |data| self.device_hmac(data))? @@ -1082,28 +1142,36 @@ impl KeyMintTa { fn send_root_of_trust(&mut self, root_of_trust: &[u8]) -> Result<(), Error> { if !self.is_strongbox() { - return Err(km_err!(Unimplemented, "root-of-trust delivery only for StrongBox")); + return Err(km_err!( + Unimplemented, + "root-of-trust delivery only for StrongBox" + )); } let mac0 = coset::CoseMac0::from_tagged_slice(root_of_trust) .map_err(|_e| km_err!(InvalidArgument, "Failed to CBOR-decode CoseMac0"))?; mac0.verify_tag(&self.rot_challenge, |tag, data| { match self.verify_device_hmac(data, tag) { Ok(true) => Ok(()), - Ok(false) => { - Err(km_err!(VerificationFailed, "HMAC verification of RootOfTrust failed")) - } + Ok(false) => Err(km_err!( + VerificationFailed, + "HMAC verification of RootOfTrust failed" + )), Err(e) => Err(e), } })?; - let payload = - mac0.payload.ok_or_else(|| km_err!(InvalidArgument, "Missing payload in CoseMac0"))?; + let payload = mac0 + .payload + .ok_or_else(|| km_err!(InvalidArgument, "Missing payload in CoseMac0"))?; let boot_info = keymint::BootInfo::from_tagged_slice(&payload) .map_err(|_e| km_err!(InvalidArgument, "Failed to CBOR-decode RootOfTrust"))?; if self.boot_info.is_none() { info!("Setting boot_info to TEE-provided {:?}", boot_info); self.boot_info = Some(boot_info); } else { - info!("Ignoring TEE-provided RootOfTrust {:?} as already set", boot_info); + info!( + "Ignoring TEE-provided RootOfTrust {:?} as already set", + boot_info + ); } Ok(()) } @@ -1115,7 +1183,11 @@ impl KeyMintTa { warn!("ignoring non-allowlisted tag: {tag:?}"); continue; } - match self.additional_attestation_info.iter().find(|&x| x.tag() == tag) { + match self + .additional_attestation_info + .iter() + .find(|&x| x.tag() == tag) + { Some(value) if value == ¶m => { warn!( concat!( @@ -1155,7 +1227,10 @@ impl KeyMintTa { // Check that the keyblob is indeed a storage key. let chars = keyblob.characteristics_at(self.hw_info.security_level)?; if !get_bool_tag_value!(chars, StorageKey)? { - return Err(km_err!(InvalidArgument, "attempting to convert non-storage key")); + return Err(km_err!( + InvalidArgument, + "attempting to convert non-storage key" + )); } // Now that we've got the key material, use a device-specific method to re-wrap it @@ -1205,7 +1280,10 @@ impl KeyMintTa { fn root_of_trust(&self) -> Result<&[u8], Error> { match &self.rot_data { Some(data) => Ok(data), - None => Err(km_err!(HardwareNotYetAvailable, "No root-of-trust info available")), + None => Err(km_err!( + HardwareNotYetAvailable, + "No root-of-trust info available" + )), } } @@ -1248,12 +1326,18 @@ impl KeyMintTa { /// Create an OK response structure with the given inner response message. fn op_ok_rsp(rsp: PerformOpRsp) -> PerformOpResponse { // Zero is OK in any context. - PerformOpResponse { error_code: 0, rsp: Some(rsp) } + PerformOpResponse { + error_code: 0, + rsp: Some(rsp), + } } /// Create a response structure with the given error code. fn error_rsp(error_code: i32) -> PerformOpResponse { - PerformOpResponse { error_code, rsp: None } + PerformOpResponse { + error_code, + rsp: None, + } } /// Create a response structure with the given error. diff --git a/libs/rust/ta/src/operation.rs b/libs/rust/ta/src/operation.rs index d3f880c..c4a9ee3 100644 --- a/libs/rust/ta/src/operation.rs +++ b/libs/rust/ta/src/operation.rs @@ -147,12 +147,22 @@ impl AuthInfo { Ok(None) } else if let Some(auth_type) = auth_type { if no_auth_required { - Err(km_err!(InvalidKeyBlob, "found both NO_AUTH_REQUIRED and USER_SECURE_ID")) + Err(km_err!( + InvalidKeyBlob, + "found both NO_AUTH_REQUIRED and USER_SECURE_ID" + )) } else { - Ok(Some(AuthInfo { secure_ids, auth_type, timeout_secs })) + Ok(Some(AuthInfo { + secure_ids, + auth_type, + timeout_secs, + })) } } else { - Err(km_err!(KeyUserNotAuthenticated, "found USER_SECURE_ID but no USER_AUTH_TYPE")) + Err(km_err!( + KeyUserNotAuthenticated, + "found USER_SECURE_ID but no USER_AUTH_TYPE" + )) } } } @@ -169,7 +179,10 @@ impl crate::KeyMintTa { // Parse and decrypt the keyblob, which requires extra hidden params. let (keyblob, sdd_slot) = self.keyblob_parse_decrypt(key_blob, ¶ms)?; - let keyblob::PlaintextKeyBlob { characteristics, key_material } = keyblob; + let keyblob::PlaintextKeyBlob { + characteristics, + key_material, + } = keyblob; // Validate parameters. let key_chars = @@ -423,7 +436,11 @@ impl crate::KeyMintTa { info!("this operation requires proof-of-presence"); self.presence_required_op = Some(op_handle); } - Ok(InternalBeginResult { challenge, params: ret_params, op_handle: op_handle.0 }) + Ok(InternalBeginResult { + challenge, + params: ret_params, + op_handle: op_handle.0, + }) } pub(crate) fn op_update_aad( @@ -439,7 +456,10 @@ impl crate::KeyMintTa { } match &mut op.crypto_op { CryptoOperation::AesGcm(op) => op.update_aad(data), - _ => Err(km_err!(InvalidOperation, "operation does not support update_aad")), + _ => Err(km_err!( + InvalidOperation, + "operation does not support update_aad" + )), } }) } @@ -546,17 +566,29 @@ impl crate::KeyMintTa { op.check_size(data.map_or(0, |v| v.len()))?; let result = match op.crypto_op { CryptoOperation::Aes(mut op) => { - let mut result = if let Some(data) = data { op.update(data)? } else { Vec::new() }; + let mut result = if let Some(data) = data { + op.update(data)? + } else { + Vec::new() + }; result.try_extend_from_slice(&op.finish()?)?; Ok(result) } CryptoOperation::AesGcm(mut op) => { - let mut result = if let Some(data) = data { op.update(data)? } else { Vec::new() }; + let mut result = if let Some(data) = data { + op.update(data)? + } else { + Vec::new() + }; result.try_extend_from_slice(&op.finish()?)?; Ok(result) } CryptoOperation::Des(mut op) => { - let mut result = if let Some(data) = data { op.update(data)? } else { Vec::new() }; + let mut result = if let Some(data) = data { + op.update(data)? + } else { + Vec::new() + }; result.try_extend_from_slice(&op.finish()?)?; Ok(result) } @@ -649,7 +681,10 @@ impl crate::KeyMintTa { )); } if !self.in_early_boot && get_bool_tag_value!(key_chars, EarlyBootOnly)? { - return Err(km_err!(EarlyBootEnded, "attempt to use EARLY_BOOT key after early boot")); + return Err(km_err!( + EarlyBootEnded, + "attempt to use EARLY_BOOT key after early boot" + )); } if let Some(max_uses) = get_opt_tag_value!(key_chars, MaxUsesPerBoot)? { @@ -672,7 +707,10 @@ impl crate::KeyMintTa { // Common check: confirm the HMAC tag in the token is valid. let mac_input = crate::hardware_auth_token_mac_input(&auth_token)?; if !self.verify_device_hmac(&mac_input, &auth_token.mac)? { - return Err(km_err!(KeyUserNotAuthenticated, "failed to authenticate auth_token")); + return Err(km_err!( + KeyUserNotAuthenticated, + "failed to authenticate auth_token" + )); } // Common check: token's auth type should match key's USER_AUTH_TYPE. if (auth_token.authenticator_type as u32 & auth_info.auth_type) == 0 { @@ -729,22 +767,39 @@ impl crate::KeyMintTa { )); } if self.verify_device_hmac(data, token).map_err(|e| { - km_err!(VerificationFailed, "failed to perform HMAC on confirmation token: {:?}", e) + km_err!( + VerificationFailed, + "failed to perform HMAC on confirmation token: {:?}", + e + ) })? { Ok(()) } else { - Err(km_err!(NoUserConfirmation, "trusted confirmation token did not match")) + Err(km_err!( + NoUserConfirmation, + "trusted confirmation token did not match" + )) } } else { - Err(km_err!(NoUserConfirmation, "no trusted confirmation token provided")) + Err(km_err!( + NoUserConfirmation, + "no trusted confirmation token provided" + )) } } /// Return the index of a free slot in the operations table. fn new_operation_index(&mut self) -> Result { - self.operations.iter().position(Option::is_none).ok_or_else(|| { - km_err!(TooManyOperations, "current op count {} >= limit", self.operations.len()) - }) + self.operations + .iter() + .position(Option::is_none) + .ok_or_else(|| { + km_err!( + TooManyOperations, + "current op count {} >= limit", + self.operations.len() + ) + }) } /// Return a new operation handle value that is not currently in use in the @@ -768,7 +823,13 @@ impl crate::KeyMintTa { Some(_op) => false, None => false, }) - .ok_or_else(|| km_err!(InvalidOperation, "operation handle {:?} not found", op_handle)) + .ok_or_else(|| { + km_err!( + InvalidOperation, + "operation handle {:?} not found", + op_handle + ) + }) } /// Execute the provided lambda over the associated [`Operation`], handling diff --git a/libs/rust/ta/src/rkp.rs b/libs/rust/ta/src/rkp.rs index 6fb8ba2..5c317e6 100644 --- a/libs/rust/ta/src/rkp.rs +++ b/libs/rust/ta/src/rkp.rs @@ -60,8 +60,9 @@ impl KeyMintTa { /// structure in ProtectedData.aidl for IRPC HAL version 2 and as per `UdsCerts` structure in /// IRPC HAL version 3. pub fn uds_certs(&self) -> Result, Error> { - let dice_info = - self.get_dice_info().ok_or_else(|| rpc_err!(Failed, "DICE info not available."))?; + let dice_info = self + .get_dice_info() + .ok_or_else(|| rpc_err!(Failed, "DICE info not available."))?; try_to_vec(&dice_info.pub_dice_artifacts.uds_certs) } @@ -74,7 +75,10 @@ impl KeyMintTa { fn rpc_device_info_cbor(&self) -> Result { // First make sure all the relevant info is available. let ids = self.get_attestation_ids().ok_or_else(|| { - km_err!(AttestationIdsNotProvisioned, "attestation ID info not available") + km_err!( + AttestationIdsNotProvisioned, + "attestation ID info not available" + ) })?; let boot_info = self .boot_info @@ -91,7 +95,11 @@ impl KeyMintTa { let model = String::from_utf8_lossy(&ids.model); let device = String::from_utf8_lossy(&ids.device); - let bootloader_state = if boot_info.device_boot_locked { "locked" } else { "unlocked" }; + let bootloader_state = if boot_info.device_boot_locked { + "locked" + } else { + "unlocked" + }; let vbmeta_digest = cbor::value::Value::Bytes(try_to_vec(&boot_info.verified_boot_hash)?); let vb_state = match boot_info.verified_boot_state { VerifiedBootState::Verified => "green", @@ -180,7 +188,12 @@ impl KeyMintTa { None, test_mode, )?, - _ => return Err(km_err!(InvalidKeyBlob, "expected key material of type variant EC.")), + _ => { + return Err(km_err!( + InvalidKeyBlob, + "expected key material of type variant EC." + )) + } }; let pub_cose_key_encoded = pub_cose_key.to_vec().map_err(CborError::from)?; let maced_pub_key = @@ -189,7 +202,9 @@ impl KeyMintTa { if test_mode == rpc::TestMode(true) { return hmac_sha256(&*self.imp.hmac, &[0; 32], data); } - self.dev.rpc.compute_hmac_sha256(&*self.imp.hmac, &*self.imp.hkdf, data) + self.dev + .rpc + .compute_hmac_sha256(&*self.imp.hmac, &*self.imp.hkdf, data) })?; let key_result = self.finish_keyblob_creation( @@ -200,7 +215,12 @@ impl KeyMintTa { keyblob::SlotPurpose::KeyGeneration, )?; - Ok((MacedPublicKey { maced_key: maced_pub_key }, key_result.key_blob)) + Ok(( + MacedPublicKey { + maced_key: maced_pub_key, + }, + key_result.key_blob, + )) } pub(crate) fn generate_cert_req( @@ -211,10 +231,16 @@ impl KeyMintTa { _challenge: &[u8], ) -> Result<(DeviceInfo, ProtectedData, Vec), Error> { if self.rpc_info.get_version() > IRPC_V2 { - return Err(rpc_err!(Removed, "generate_cert_req is not supported in IRPC V3+ HAL.")); + return Err(rpc_err!( + Removed, + "generate_cert_req is not supported in IRPC V3+ HAL." + )); } let _device_info = self.rpc_device_info()?; - Err(km_err!(Unimplemented, "GenerateCertificateRequest is only required for RKP before v3")) + Err(km_err!( + Unimplemented, + "GenerateCertificateRequest is only required for RKP before v3" + )) } pub(crate) fn generate_cert_req_v2( @@ -264,11 +290,16 @@ impl KeyMintTa { cose_mac0.verify_tag(&[], |expected_tag, data| -> Result<(), Error> { let computed_tag = - self.dev.rpc.compute_hmac_sha256(&*self.imp.hmac, &*self.imp.hkdf, data)?; + self.dev + .rpc + .compute_hmac_sha256(&*self.imp.hmac, &*self.imp.hkdf, data)?; if self.imp.compare.eq(expected_tag, &computed_tag) { Ok(()) } else { - Err(rpc_err!(InvalidMac, "invalid tag found in a MacedPublicKey")) + Err(rpc_err!( + InvalidMac, + "invalid tag found in a MacedPublicKey" + )) } })?; } @@ -282,13 +313,16 @@ impl KeyMintTa { ])?; let csr_payload_data = serialize_cbor(&csr_payload)?; // Construct the payload for `SignedData` - let signed_data_payload = - cbor!([Value::Bytes(challenge.to_vec()), Value::Bytes(csr_payload_data)])?; + let signed_data_payload = cbor!([ + Value::Bytes(challenge.to_vec()), + Value::Bytes(csr_payload_data) + ])?; let signed_data_payload_data = serialize_cbor(&signed_data_payload)?; // Process DICE info. - let dice_info = - self.get_dice_info().ok_or_else(|| rpc_err!(Failed, "DICE info not available."))?; + let dice_info = self + .get_dice_info() + .ok_or_else(|| rpc_err!(Failed, "DICE info not available."))?; let uds_certs = read_to_value(&dice_info.pub_dice_artifacts.uds_certs)?; let dice_cert_chain = read_to_value(&dice_info.pub_dice_artifacts.dice_cert_chain)?; @@ -317,7 +351,9 @@ fn build_maced_pub_key(pub_cose_key: Vec, compute_mac: F) -> Result Result, Error>, { - let protected = HeaderBuilder::new().algorithm(iana::Algorithm::HMAC_256_256).build(); + let protected = HeaderBuilder::new() + .algorithm(iana::Algorithm::HMAC_256_256) + .build(); let cose_mac_0 = CoseMac0Builder::new() .protected(protected) .payload(pub_cose_key) diff --git a/libs/rust/ta/src/secret.rs b/libs/rust/ta/src/secret.rs index d35fe92..301791d 100644 --- a/libs/rust/ta/src/secret.rs +++ b/libs/rust/ta/src/secret.rs @@ -24,7 +24,10 @@ impl crate::KeyMintTa { if self.shared_secret_params.is_none() { let mut nonce = vec_try![0u8; 32]?; self.imp.rng.fill_bytes(&mut nonce); - self.shared_secret_params = Some(SharedSecretParameters { seed: Vec::new(), nonce }); + self.shared_secret_params = Some(SharedSecretParameters { + seed: Vec::new(), + nonce, + }); } Ok(self.shared_secret_params.as_ref().unwrap().clone()) // safe: filled above } @@ -33,10 +36,18 @@ impl crate::KeyMintTa { &mut self, params: &[SharedSecretParameters], ) -> Result, Error> { - info!("Setting HMAC key from {} shared secret parameters", params.len()); + info!( + "Setting HMAC key from {} shared secret parameters", + params.len() + ); let local_params = match &self.shared_secret_params { Some(params) => params, - None => return Err(km_err!(HardwareNotYetAvailable, "no local shared secret params")), + None => { + return Err(km_err!( + HardwareNotYetAvailable, + "no local shared secret params" + )) + } }; let context = shared_secret_context(params, local_params)?; @@ -67,7 +78,11 @@ pub fn shared_secret_context( for param in params { result.try_extend_from_slice(¶m.seed)?; if param.nonce.len() != 32 { - return Err(km_err!(InvalidArgument, "nonce len {} not 32", param.nonce.len())); + return Err(km_err!( + InvalidArgument, + "nonce len {} not 32", + param.nonce.len() + )); } result.try_extend_from_slice(¶m.nonce)?; if param == must_include { @@ -75,7 +90,10 @@ pub fn shared_secret_context( } } if !seen { - Err(km_err!(InvalidArgument, "shared secret params missing local value")) + Err(km_err!( + InvalidArgument, + "shared secret params missing local value" + )) } else { Ok(result) } diff --git a/libs/rust/ta/src/tests.rs b/libs/rust/ta/src/tests.rs index 074061a..af680d7 100644 --- a/libs/rust/ta/src/tests.rs +++ b/libs/rust/ta/src/tests.rs @@ -98,12 +98,22 @@ fn test_secure_key_wrapper() { let encoded_bytes = hex::decode(encoded_str).unwrap(); let secure_key_wrapper = SecureKeyWrapper::from_der(&encoded_bytes).unwrap(); assert_eq!(secure_key_wrapper.version, 0); - let key_format: KeyFormat = secure_key_wrapper.key_description.key_format.try_into().unwrap(); + let key_format: KeyFormat = secure_key_wrapper + .key_description + .key_format + .try_into() + .unwrap(); assert_eq!(KeyFormat::Raw, key_format); let authz = secure_key_wrapper.key_description.key_params.auths; let purpose_values: Vec = authz .iter() - .filter_map(|param| if let KeyParam::Purpose(v) = param { Some(*v) } else { None }) + .filter_map(|param| { + if let KeyParam::Purpose(v) = param { + Some(*v) + } else { + None + } + }) .collect(); assert_eq!(purpose_values.len(), 2); assert!(purpose_values.contains(&KeyPurpose::Encrypt)); @@ -209,7 +219,10 @@ fn test_key_description_encode_decode() { let secure_key_wrapper = SecureKeyWrapper::from_der(&encoded_bytes).unwrap(); let key_description = secure_key_wrapper.key_description; let encoded_key_description_got = key_description.to_der().unwrap(); - assert_eq!(hex::encode(encoded_key_description_got), encoded_key_description_want); + assert_eq!( + hex::encode(encoded_key_description_got), + encoded_key_description_want + ); } #[test] @@ -218,11 +231,17 @@ fn test_split_rsp_invalid_input() { let rsp = vec![]; let result = split_rsp(&rsp, 5); assert!(result.is_err()); - assert!(matches!(result, Err(Error::Hal(ErrorCode::InvalidArgument, _)))); + assert!(matches!( + result, + Err(Error::Hal(ErrorCode::InvalidArgument, _)) + )); let rsp = vec![0x82, 0x21, 0x80]; let result = split_rsp(&rsp, 1); - assert!(matches!(result, Err(Error::Hal(ErrorCode::InvalidArgument, _)))); + assert!(matches!( + result, + Err(Error::Hal(ErrorCode::InvalidArgument, _)) + )); } #[test] @@ -231,7 +250,10 @@ fn test_split_rsp_smaller_input() { let rsp = vec![0x82, 0x13, 0x82, 0x80, 0x80]; let result = split_rsp(&rsp, 20).expect("result should not be error"); assert_eq!(result.len(), 1); - let inner_msg = result.first().expect("single message is expected").as_slice(); + let inner_msg = result + .first() + .expect("single message is expected") + .as_slice(); assert_eq!(inner_msg.len(), 6); let marker = inner_msg[0]; assert_eq!(marker, NEXT_MESSAGE_SIGNAL_FALSE); @@ -245,7 +267,10 @@ fn test_split_rsp_allowed_size_input() { let rsp = vec![0x82, 0x13, 0x82, 0x80, 0x80]; let result = split_rsp(&rsp, 6).expect("result should not be error"); assert_eq!(result.len(), 1); - let inner_msg = result.first().expect("single message is expected").as_slice(); + let inner_msg = result + .first() + .expect("single message is expected") + .as_slice(); assert_eq!(inner_msg.len(), 6); let marker = inner_msg[0]; assert_eq!(marker, NEXT_MESSAGE_SIGNAL_FALSE); @@ -260,13 +285,19 @@ fn test_split_rsp_max_size_input() { let result = split_rsp(&rsp, 6).expect("result should not be error"); assert_eq!(result.len(), 2); - let inner_msg1 = result.first().expect("a message is expected at index 0").as_slice(); + let inner_msg1 = result + .first() + .expect("a message is expected at index 0") + .as_slice(); assert_eq!(inner_msg1.len(), 6); let marker1 = inner_msg1[0]; assert_eq!(marker1, NEXT_MESSAGE_SIGNAL_TRUE); assert_eq!(&inner_msg1[1..], &rsp[..5]); - let inner_msg2 = result.get(1).expect("a message is expected at index 1").as_slice(); + let inner_msg2 = result + .get(1) + .expect("a message is expected at index 1") + .as_slice(); assert_eq!(inner_msg2.len(), 2); let marker2 = inner_msg2[0]; assert_eq!(marker2, NEXT_MESSAGE_SIGNAL_FALSE); @@ -286,21 +317,30 @@ fn test_split_rsp_larger_input_perfect_split() { let result = split_rsp(&rsp, 6).expect("result should not be error"); assert_eq!(result.len(), 3); - let inner_msg1 = result.first().expect("a message is expected at index 0").as_slice(); + let inner_msg1 = result + .first() + .expect("a message is expected at index 0") + .as_slice(); assert_eq!(inner_msg1.len(), 6); let marker1 = inner_msg1[0]; assert_eq!(marker1, NEXT_MESSAGE_SIGNAL_TRUE); let msg1 = &inner_msg1[1..]; assert_eq!(msg1, rsp1); - let inner_msg2 = result.get(1).expect("a message is expected at index 1").as_slice(); + let inner_msg2 = result + .get(1) + .expect("a message is expected at index 1") + .as_slice(); assert_eq!(inner_msg2.len(), 6); let marker2 = inner_msg2[0]; assert_eq!(marker2, NEXT_MESSAGE_SIGNAL_TRUE); let msg2 = &inner_msg2[1..]; assert_eq!(msg2, rsp2); - let inner_msg3 = result.get(2).expect("a message is expected at index 2").as_slice(); + let inner_msg3 = result + .get(2) + .expect("a message is expected at index 2") + .as_slice(); assert_eq!(inner_msg3.len(), 6); let marker3 = inner_msg3[0]; assert_eq!(marker3, NEXT_MESSAGE_SIGNAL_FALSE); @@ -321,21 +361,30 @@ fn test_split_rsp_larger_input_imperfect_split() { let result = split_rsp(&rsp, 6).expect("result should not be error"); assert_eq!(result.len(), 3); - let inner_msg1 = result.first().expect("a message is expected at index 0").as_slice(); + let inner_msg1 = result + .first() + .expect("a message is expected at index 0") + .as_slice(); assert_eq!(inner_msg1.len(), 6); let marker1 = inner_msg1[0]; assert_eq!(marker1, NEXT_MESSAGE_SIGNAL_TRUE); let msg1 = &inner_msg1[1..]; assert_eq!(msg1, rsp1); - let inner_msg2 = result.get(1).expect("a message is expected at index 1").as_slice(); + let inner_msg2 = result + .get(1) + .expect("a message is expected at index 1") + .as_slice(); assert_eq!(inner_msg2.len(), 6); let marker2 = inner_msg2[0]; assert_eq!(marker2, NEXT_MESSAGE_SIGNAL_TRUE); let msg2 = &inner_msg2[1..]; assert_eq!(msg2, rsp2); - let inner_msg3 = result.get(2).expect("a message is expected at index 2").as_slice(); + let inner_msg3 = result + .get(2) + .expect("a message is expected at index 2") + .as_slice(); assert_eq!(inner_msg3.len(), 2); let marker3 = inner_msg3[0]; assert_eq!(marker3, NEXT_MESSAGE_SIGNAL_FALSE); diff --git a/libs/rust/tests/src/bin/auth-keyblob-parse.rs b/libs/rust/tests/src/bin/auth-keyblob-parse.rs index dee84a3..c5e29d0 100644 --- a/libs/rust/tests/src/bin/auth-keyblob-parse.rs +++ b/libs/rust/tests/src/bin/auth-keyblob-parse.rs @@ -48,7 +48,11 @@ const SOFTWARE_ROOT_OF_TRUST: &[u8] = b"SW"; /// Remove all instances of some tags from a set of `KeyParameter`s. pub fn remove_tags(params: &[KeyParam], tags: &[keymint::Tag]) -> Vec { - params.iter().filter(|p| !tags.contains(&p.tag())).cloned().collect() + params + .iter() + .filter(|p| !tags.contains(&p.tag())) + .cloned() + .collect() } fn process(filename: &str, hex: bool) { @@ -92,7 +96,10 @@ fn process(filename: &str, hex: bool) { { // Also round-trip the keyblob to binary and expect to get back where we started. let regenerated_data = keyblob.serialize(&hmac, &hidden).unwrap(); - assert_eq!(®enerated_data[..regenerated_data.len()], &data[..data.len()]); + assert_eq!( + ®enerated_data[..regenerated_data.len()], + &data[..data.len()] + ); } // Create a PlaintextKeyBlob from the data. @@ -155,7 +162,9 @@ fn process(filename: &str, hex: bool) { let mut keygen_params = filtered.clone(); match tag::get_algorithm(&filtered).unwrap() { Algorithm::Ec | Algorithm::Rsa => { - keygen_params.push(KeyParam::CertificateNotBefore(DateTime { ms_since_epoch: 0 })); + keygen_params.push(KeyParam::CertificateNotBefore(DateTime { + ms_since_epoch: 0, + })); keygen_params.push(KeyParam::CertificateNotAfter(DateTime { ms_since_epoch: 1_900_000_000_000, })); diff --git a/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs b/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs index 73bfb2f..c18b86d 100644 --- a/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs +++ b/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs @@ -70,7 +70,10 @@ fn process(filename: &str, hex: bool) { if let Some(addl_info) = keyblob.addl_info { println!(" addl_info={}", addl_info); } - println!(" hw_enforced={:?},\n sw_enforced={:?},", keyblob.hw_enforced, keyblob.sw_enforced); + println!( + " hw_enforced={:?},\n sw_enforced={:?},", + keyblob.hw_enforced, keyblob.sw_enforced + ); if let Some(key_slot) = keyblob.key_slot { println!(" key_slot={}", key_slot); } diff --git a/libs/rust/tests/src/lib.rs b/libs/rust/tests/src/lib.rs index 7c0fff9..0300702 100644 --- a/libs/rust/tests/src/lib.rs +++ b/libs/rust/tests/src/lib.rs @@ -154,7 +154,12 @@ pub fn test_hkdf(hmac: H) { let info = hex::decode(test.info).unwrap(); let got = hmac.hkdf(&salt, &ikm, &info, test.out_len).unwrap(); - assert_eq!(hex::encode(got), test.want, "incorrect HKDF result for case {}", i); + assert_eq!( + hex::encode(got), + test.want, + "incorrect HKDF result for case {}", + i + ); } } @@ -327,7 +332,9 @@ pub fn test_hmac(hmac: H) { ]; for (i, test) in HMAC_TESTS.iter().enumerate() { - let mut op = hmac.begin(hmac::Key(test.key.to_vec()).into(), test.digest).unwrap(); + let mut op = hmac + .begin(hmac::Key(test.key.to_vec()).into(), test.digest) + .unwrap(); op.update(test.data).unwrap(); let mut mac = op.finish().unwrap(); mac.truncate(test.tag_size); @@ -375,10 +382,15 @@ pub fn test_ckdf(kdf: T) { let v2 = vec![0x02, 0x02, 0x02, 0x02]; let v3 = vec![0x03, 0x03, 0x03, 0x03]; - let result = kdf.ckdf(&key.into(), label, &[&v0, &v1, &v2, &v3], 32).unwrap(); + let result = kdf + .ckdf(&key.into(), label, &[&v0, &v1, &v2, &v3], 32) + .unwrap(); assert_eq!( hex::encode(result), - concat!("ac9af88a02241f53d43056a4676c42ee", "f06825755e419e7bd20f4e57487717aa") + concat!( + "ac9af88a02241f53d43056a4676c42ee", + "f06825755e419e7bd20f4e57487717aa" + ) ); } @@ -424,7 +436,9 @@ pub fn test_aes_gcm(aes: A) { let mut op = aes .begin_aead( aes_key.into(), - aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() }, + aes::GcmMode::GcmTag16 { + nonce: iv.clone().try_into().unwrap(), + }, SymmetricOperation::Encrypt, ) .unwrap(); @@ -437,7 +451,9 @@ pub fn test_aes_gcm(aes: A) { let mut op = aes .begin_aead( aes_key.into(), - aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() }, + aes::GcmMode::GcmTag16 { + nonce: iv.clone().try_into().unwrap(), + }, SymmetricOperation::Decrypt, ) .unwrap(); @@ -450,7 +466,9 @@ pub fn test_aes_gcm(aes: A) { let aes_key = aes::Key::new(key.clone()).unwrap(); let mut op = match aes.begin_aead( aes_key.into(), - aes::GcmMode::GcmTag12 { nonce: iv.clone().try_into().unwrap() }, + aes::GcmMode::GcmTag12 { + nonce: iv.clone().try_into().unwrap(), + }, SymmetricOperation::Decrypt, ) { Ok(c) => c, @@ -465,7 +483,9 @@ pub fn test_aes_gcm(aes: A) { let aes_key = aes::Key::new(key).unwrap(); let mut op = match aes.begin_aead( aes_key.into(), - aes::GcmMode::GcmTag12 { nonce: iv.try_into().unwrap() }, + aes::GcmMode::GcmTag12 { + nonce: iv.try_into().unwrap(), + }, SymmetricOperation::Decrypt, ) { Ok(c) => c, @@ -505,14 +525,22 @@ pub fn test_des(des: D) { let des_key = des::Key::new(key.clone()).unwrap(); let mut op = des - .begin(des_key.clone().into(), des::Mode::EcbNoPadding, SymmetricOperation::Encrypt) + .begin( + des_key.clone().into(), + des::Mode::EcbNoPadding, + SymmetricOperation::Encrypt, + ) .unwrap(); let mut got_ct = op.update(&msg).unwrap(); got_ct.extend_from_slice(&op.finish().unwrap()); assert_eq!(test.ct, hex::encode(&got_ct)); let mut op = des - .begin(des_key.into(), des::Mode::EcbNoPadding, SymmetricOperation::Decrypt) + .begin( + des_key.into(), + des::Mode::EcbNoPadding, + SymmetricOperation::Decrypt, + ) .unwrap(); let mut got_pt = op.update(&got_ct).unwrap(); got_pt.extend_from_slice(&op.finish().unwrap()); @@ -538,7 +566,12 @@ pub fn test_sha256(sha256: S) { ]; for test in tests { let got = sha256.hash(test.msg).unwrap(); - assert_eq!(hex::encode(got), test.want, "for input {}", hex::encode(test.msg)); + assert_eq!( + hex::encode(got), + test.want, + "for input {}", + hex::encode(test.msg) + ); } } @@ -546,12 +579,16 @@ pub fn test_sha256(sha256: S) { /// /// Warning: this test will use slots in the provided manager, and may leak slots on failure. pub fn test_sdd_mgr(mut sdd_mgr: M, mut rng: R) { - let (slot1, sdd1) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + let (slot1, sdd1) = sdd_mgr + .new_secret(&mut rng, SlotPurpose::KeyGeneration) + .unwrap(); assert!(sdd_mgr.get_secret(slot1).unwrap() == sdd1); assert!(sdd_mgr.get_secret(slot1).unwrap() == sdd1); // A second instance should share factory reset secret but not per-key secret. - let (slot2, sdd2) = sdd_mgr.new_secret(&mut rng, SlotPurpose::KeyGeneration).unwrap(); + let (slot2, sdd2) = sdd_mgr + .new_secret(&mut rng, SlotPurpose::KeyGeneration) + .unwrap(); assert!(sdd_mgr.get_secret(slot2).unwrap() == sdd2); assert_eq!(sdd1.factory_reset_secret, sdd2.factory_reset_secret); assert_ne!(sdd1.secure_deletion_secret, sdd2.secure_deletion_secret); @@ -622,11 +659,25 @@ pub fn test_retrieve_rpc_artifacts( assert!(rpc.get_dice_info(rpc::TestMode(false)).is_ok()); let context = b"abcdef"; - let data1 = rpc.derive_bytes_from_hbk(hkdf, context, 16).expect("failed to derive from HBK"); - let data2 = rpc.derive_bytes_from_hbk(hkdf, context, 16).expect("failed to derive from HBK"); - assert_eq!(data1, data2, "derive_bytes_from_hbk() method should be deterministic"); + let data1 = rpc + .derive_bytes_from_hbk(hkdf, context, 16) + .expect("failed to derive from HBK"); + let data2 = rpc + .derive_bytes_from_hbk(hkdf, context, 16) + .expect("failed to derive from HBK"); + assert_eq!( + data1, data2, + "derive_bytes_from_hbk() method should be deterministic" + ); - let data1 = rpc.compute_hmac_sha256(hmac, hkdf, context).expect("failed to perform HMAC"); - let data2 = rpc.compute_hmac_sha256(hmac, hkdf, context).expect("failed to perform HMAC"); - assert_eq!(data1, data2, "compute_hmac_sha256() method should be deterministic"); + let data1 = rpc + .compute_hmac_sha256(hmac, hkdf, context) + .expect("failed to perform HMAC"); + let data2 = rpc + .compute_hmac_sha256(hmac, hkdf, context) + .expect("failed to perform HMAC"); + assert_eq!( + data1, data2, + "compute_hmac_sha256() method should be deterministic" + ); } diff --git a/libs/rust/tests/tests/keyblob_test.rs b/libs/rust/tests/tests/keyblob_test.rs index 431312b..dd5d745 100644 --- a/libs/rust/tests/tests/keyblob_test.rs +++ b/libs/rust/tests/tests/keyblob_test.rs @@ -88,7 +88,11 @@ fn test_serialize_authenticated_legacy_keyblob() { ), "0000000000000000", // hmac ), - KeyBlob { key_material: vec![0xbb, 0xbb], hw_enforced: vec![], sw_enforced: vec![] }, + KeyBlob { + key_material: vec![0xbb, 0xbb], + hw_enforced: vec![], + sw_enforced: vec![], + }, )]; for (hex_data, want) in tests { let mut data = hex::decode(hex_data).unwrap(); diff --git a/libs/rust/watchdog/Cargo.toml b/libs/rust/watchdog/Cargo.toml new file mode 100644 index 0000000..2ac4c91 --- /dev/null +++ b/libs/rust/watchdog/Cargo.toml @@ -0,0 +1,19 @@ +# Note that Cargo is not an officially supported build tool (Android's Soong is the official +# tool). This Cargo.toml file is included purely for the convenience of KeyMint developers. + +[package] +name = "watchdog_rs" +version = "0.1.0" +authors = ["David Drysdale "] +edition = "2021" +license = "Apache-2.0" + +[features] + + +[dependencies] +chrono = "0.4.42" +log = "0.4.28" + +[dev-dependencies] +hex = "0.4.3" diff --git a/libs/rust/watchdog/src/lib.rs b/libs/rust/watchdog/src/lib.rs new file mode 100644 index 0000000..9db70a8 --- /dev/null +++ b/libs/rust/watchdog/src/lib.rs @@ -0,0 +1,395 @@ +// Copyright 2021, 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. + +// Can be removed when instrumentations are added to keystore. +#![allow(dead_code)] + +//! This module implements a watchdog thread. + +use std::{ + cmp::min, + collections::HashMap, + sync::Arc, + sync::{Condvar, Mutex, MutexGuard}, + thread, +}; +use std::{ + marker::PhantomData, + time::{Duration, Instant}, +}; + +/// Represents a Watchdog record. It can be created with `Watchdog::watch` or +/// `Watchdog::watch_with`. It disarms the record when dropped. +pub struct WatchPoint { + id: &'static str, + wd: Arc, + not_send: PhantomData<*mut ()>, // WatchPoint must not be Send. +} + +impl Drop for WatchPoint { + fn drop(&mut self) { + self.wd.disarm(self.id) + } +} + +#[derive(Debug, PartialEq, Eq)] +enum State { + NotRunning, + Running, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct Index { + tid: thread::ThreadId, + id: &'static str, +} + +struct Record { + started: Instant, + deadline: Instant, + context: Option>, +} + +impl Record { + // Return a string representation of the start time of the record. + // + // Times are hard. This may not be accurate (e.g. if the system clock has been modified since + // the watchdog started), but it's _really_ useful to get a starting wall time for overrunning + // watchdogs. + fn started_utc(&self) -> String { + let started_utc = chrono::Utc::now() - self.started.elapsed(); + format!("{}", started_utc.format("%m-%d %H:%M:%S%.3f UTC")) + } +} + +struct WatchdogState { + state: State, + thread: Option>, + /// How long to wait before dropping the watchdog thread when idle. + idle_timeout: Duration, + records: HashMap, + last_report: Option, + noisy_timeout: Duration, +} + +impl WatchdogState { + /// If we have overdue records, we want to log them but slowly backoff + /// so that we do not clog the logs. We start with logs every + /// `MIN_REPORT_TIMEOUT` sec then increment the timeout by 5 up + /// to a maximum of `MAX_REPORT_TIMEOUT`. + const MIN_REPORT_TIMEOUT: Duration = Duration::from_secs(1); + const MAX_REPORT_TIMEOUT: Duration = Duration::from_secs(30); + + fn reset_noisy_timeout(&mut self) { + self.noisy_timeout = Self::MIN_REPORT_TIMEOUT; + } + + fn update_noisy_timeout(&mut self) { + let noisy_update = self.noisy_timeout + Duration::from_secs(5); + self.noisy_timeout = min(Self::MAX_REPORT_TIMEOUT, noisy_update); + } + + fn overdue_and_next_timeout(&self) -> (bool, Option) { + let now = Instant::now(); + let mut next_timeout: Option = None; + let mut has_overdue = false; + for (_, r) in self.records.iter() { + let timeout = r.deadline.saturating_duration_since(now); + if timeout == Duration::new(0, 0) { + // This timeout has passed. + has_overdue = true; + } else { + // This timeout is still to come; see if it's the closest one to now. + next_timeout = match next_timeout { + Some(nt) if timeout < nt => Some(timeout), + Some(nt) => Some(nt), + None => Some(timeout), + }; + } + } + (has_overdue, next_timeout) + } + + fn log_report(&mut self, has_overdue: bool) { + if !has_overdue { + // Nothing to report. + self.last_report = None; + return; + } + // Something to report... + if let Some(reported_at) = self.last_report { + if reported_at.elapsed() < self.noisy_timeout { + // .. but it's too soon since the last report. + self.last_report = None; + return; + } + } + self.update_noisy_timeout(); + self.last_report = Some(Instant::now()); + log::warn!("### Keystore Watchdog report - BEGIN ###"); + + let now = Instant::now(); + let mut overdue_records: Vec<(&Index, &Record)> = self + .records + .iter() + .filter(|(_, r)| r.deadline.saturating_duration_since(now) == Duration::new(0, 0)) + .collect(); + + log::warn!( + concat!( + "When extracting from a bug report, please include this header ", + "and all {} records below (to footer)" + ), + overdue_records.len() + ); + + // Watch points can be nested, i.e., a single thread may have multiple armed + // watch points. And the most recent on each thread (thread recent) is closest to the point + // where something is blocked. Furthermore, keystore2 has various critical section + // and common backend resources KeyMint that can only be entered serialized. So if one + // thread hangs, the others will soon follow suite. Thus the oldest "thread recent" watch + // point is most likely pointing toward the culprit. + // Thus, sort by start time first. + overdue_records.sort_unstable_by(|(_, r1), (_, r2)| r1.started.cmp(&r2.started)); + // Then we groups all of the watch points per thread preserving the order within + // groups. + let groups = overdue_records.iter().fold( + HashMap::>::new(), + |mut acc, (i, r)| { + acc.entry(i.tid).or_default().push((i, r)); + acc + }, + ); + // Put the groups back into a vector. + let mut groups: Vec> = groups.into_values().collect(); + // Sort the groups by start time of the most recent (.last()) of each group. + // It is panic safe to use unwrap() here because we never add empty vectors to + // the map. + groups.sort_by(|v1, v2| { + v1.last() + .unwrap() + .1 + .started + .cmp(&v2.last().unwrap().1.started) + }); + + for g in groups.iter() { + for (i, r) in g.iter() { + match &r.context { + Some(ctx) => { + log::warn!( + "{:?} {} Started: {} Pending: {:?} Overdue {:?} for {:?}", + i.tid, + i.id, + r.started_utc(), + r.started.elapsed(), + r.deadline.elapsed(), + ctx + ); + } + None => { + log::warn!( + "{:?} {} Started: {} Pending: {:?} Overdue {:?}", + i.tid, + i.id, + r.started_utc(), + r.started.elapsed(), + r.deadline.elapsed() + ); + } + } + } + } + log::warn!("### Keystore Watchdog report - END ###"); + } + + fn disarm(&mut self, index: Index) { + let result = self.records.remove(&index); + if let Some(record) = result { + let now = Instant::now(); + let timeout_left = record.deadline.saturating_duration_since(now); + if timeout_left == Duration::new(0, 0) { + match &record.context { + Some(ctx) => log::info!( + "Watchdog complete for: {:?} {} Started: {} Pending: {:?} Overdue {:?} for {:?}", + index.tid, + index.id, + record.started_utc(), + record.started.elapsed(), + record.deadline.elapsed(), + ctx + ), + None => log::info!( + "Watchdog complete for: {:?} {} Started: {} Pending: {:?} Overdue {:?}", + index.tid, + index.id, + record.started_utc(), + record.started.elapsed(), + record.deadline.elapsed() + ), + } + } + } + } + + fn arm(&mut self, index: Index, record: Record) { + if self.records.insert(index.clone(), record).is_some() { + log::warn!( + "Recursive watchdog record at \"{:?}\" replaces previous record.", + index + ); + } + } +} + +/// Watchdog spawns a thread that logs records of all overdue watch points when a deadline +/// is missed and at least every second as long as overdue watch points exist. +/// The thread terminates when idle for a given period of time. +pub struct Watchdog { + state: Arc<(Condvar, Mutex)>, +} + +impl Watchdog { + /// Construct a [`Watchdog`]. When `idle_timeout` has elapsed since the watchdog thread became + /// idle, i.e., there are no more active or overdue watch points, the watchdog thread + /// terminates. + pub fn new(idle_timeout: Duration) -> Arc { + Arc::new(Self { + state: Arc::new(( + Condvar::new(), + Mutex::new(WatchdogState { + state: State::NotRunning, + thread: None, + idle_timeout, + records: HashMap::new(), + last_report: None, + noisy_timeout: WatchdogState::MIN_REPORT_TIMEOUT, + }), + )), + }) + } + + fn watch_with_optional( + wd: Arc, + context: Option>, + id: &'static str, + timeout: Duration, + ) -> Option { + let Some(deadline) = Instant::now().checked_add(timeout) else { + log::warn!("Deadline computation failed for WatchPoint \"{}\"", id); + log::warn!("WatchPoint not armed."); + return None; + }; + wd.arm(context, id, deadline); + Some(WatchPoint { + id, + wd, + not_send: Default::default(), + }) + } + + /// Create a new watch point. If the WatchPoint is not dropped before the timeout + /// expires, a report is logged at least every second, which includes the id string + /// and any provided context. + pub fn watch_with( + wd: &Arc, + id: &'static str, + timeout: Duration, + context: impl std::fmt::Debug + Send + 'static, + ) -> Option { + Self::watch_with_optional(wd.clone(), Some(Box::new(context)), id, timeout) + } + + /// Like `watch_with`, but without context. + pub fn watch(wd: &Arc, id: &'static str, timeout: Duration) -> Option { + Self::watch_with_optional(wd.clone(), None, id, timeout) + } + + fn arm( + &self, + context: Option>, + id: &'static str, + deadline: Instant, + ) { + let tid = thread::current().id(); + let index = Index { tid, id }; + let record = Record { + started: Instant::now(), + deadline, + context, + }; + + let (ref condvar, ref state) = *self.state; + + let mut state = state.lock().unwrap(); + state.arm(index, record); + + if state.state != State::Running { + self.spawn_thread(&mut state); + } + drop(state); + condvar.notify_all(); + } + + fn disarm(&self, id: &'static str) { + let tid = thread::current().id(); + let index = Index { tid, id }; + let (_, ref state) = *self.state; + + let mut state = state.lock().unwrap(); + state.disarm(index); + // There is no need to notify condvar. There is no action required for the + // watchdog thread before the next deadline. + } + + fn spawn_thread(&self, state: &mut MutexGuard) { + if let Some(t) = state.thread.take() { + t.join().expect("Watchdog thread panicked."); + } + + let cloned_state = self.state.clone(); + + state.thread = Some(thread::spawn(move || { + let (ref condvar, ref state) = *cloned_state; + + let mut state = state.lock().unwrap(); + + loop { + let (has_overdue, next_timeout) = state.overdue_and_next_timeout(); + state.log_report(has_overdue); + + let (next_timeout, idle) = match (has_overdue, next_timeout) { + (true, Some(next_timeout)) => (min(next_timeout, state.noisy_timeout), false), + (true, None) => (state.noisy_timeout, false), + (false, Some(next_timeout)) => (next_timeout, false), + (false, None) => (state.idle_timeout, true), + }; + + // Wait until the closest timeout pops, but use a condition variable so that if a + // new watchpoint is started in the meanwhile it will interrupt the wait so we can + // recalculate. + let (s, timeout) = condvar.wait_timeout(state, next_timeout).unwrap(); + state = s; + + if idle && timeout.timed_out() && state.records.is_empty() { + state.reset_noisy_timeout(); + state.state = State::NotRunning; + break; + } + } + log::info!("Watchdog thread idle -> terminating. Have a great day."); + })); + state.state = State::Running; + } +} diff --git a/libs/rust/wire/src/keymint.rs b/libs/rust/wire/src/keymint.rs index ec381de..386bb2e 100644 --- a/libs/rust/wire/src/keymint.rs +++ b/libs/rust/wire/src/keymint.rs @@ -56,7 +56,9 @@ pub const NEXT_MESSAGE_SIGNAL_FALSE: u8 = 0b00000000u8; pub const UNDEFINED_NOT_BEFORE: DateTime = DateTime { ms_since_epoch: 0 }; /// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to /// 9999-12-31T23:59:59Z. -pub const UNDEFINED_NOT_AFTER: DateTime = DateTime { ms_since_epoch: 253402300799000 }; +pub const UNDEFINED_NOT_AFTER: DateTime = DateTime { + ms_since_epoch: 253402300799000, +}; /// Possible verified boot state values. #[derive(Clone, Copy, Debug, PartialEq, Eq, N, AsCborValue)] @@ -110,7 +112,9 @@ pub struct DateTime { impl AsCborValue for DateTime { fn from_cbor_value(value: cbor::value::Value) -> Result { let val = ::from_cbor_value(value)?; - Ok(Self { ms_since_epoch: val }) + Ok(Self { + ms_since_epoch: val, + }) } fn to_cbor_value(self) -> Result { self.ms_since_epoch.to_cbor_value() @@ -709,7 +713,10 @@ impl crate::AsCborValue for KeyParam { #[cfg(feature = "hal_v4")] KeyParam::ModuleHash(v) => (Tag::ModuleHash, v.to_cbor_value()?), }; - Ok(cbor::value::Value::Array(vec_try![tag.to_cbor_value()?, val]?)) + Ok(cbor::value::Value::Array(vec_try![ + tag.to_cbor_value()?, + val + ]?)) } fn cddl_typename() -> Option { Some("KeyParam".to_string()) @@ -735,8 +742,12 @@ impl crate::AsCborValue for KeyParam { PaddingMode::cddl_ref(), "Tag_Padding", ); - result += - &format!(" [{}, {}], ; {}\n", Tag::Digest as i32, Digest::cddl_ref(), "Tag_Digest",); + result += &format!( + " [{}, {}], ; {}\n", + Tag::Digest as i32, + Digest::cddl_ref(), + "Tag_Digest", + ); result += &format!( " [{}, {}], ; {}\n", Tag::EcCurve as i32, @@ -839,8 +850,12 @@ impl crate::AsCborValue for KeyParam { u32::cddl_ref(), "Tag_UsageCountLimit", ); - result += - &format!(" [{}, {}], ; {}\n", Tag::UserId as i32, u32::cddl_ref(), "Tag_UserId",); + result += &format!( + " [{}, {}], ; {}\n", + Tag::UserId as i32, + u32::cddl_ref(), + "Tag_UserId", + ); result += &format!( " [{}, {}], ; {}\n", Tag::UserSecureId as i32, @@ -1012,8 +1027,12 @@ impl crate::AsCborValue for KeyParam { "true", "Tag_DeviceUniqueAttestation", ); - result += - &format!(" [{}, {}], ; {}\n", Tag::StorageKey as i32, "true", "Tag_StorageKey",); + result += &format!( + " [{}, {}], ; {}\n", + Tag::StorageKey as i32, + "true", + "Tag_StorageKey", + ); result += &format!( " [{}, {}], ; {}\n", Tag::Nonce as i32, diff --git a/libs/rust/wire/src/legacy.rs b/libs/rust/wire/src/legacy.rs index 3fbc9db..6acd140 100644 --- a/libs/rust/wire/src/legacy.rs +++ b/libs/rust/wire/src/legacy.rs @@ -58,10 +58,10 @@ use crate::{ keymint::{Algorithm, ErrorCode, VerifiedBootState}, try_from_n, }; -use Vec; use enumn::N; use kmr_derive::LegacySerialize; use zeroize::ZeroizeOnDrop; +use Vec; /// This bit is set in the `u32` command value for response messages. const TRUSTY_RESPONSE_BITMASK: u32 = 0x01; @@ -193,7 +193,8 @@ fn serialize_trusty_response_message( // mark this as the final response. let raw_cmd = cmd << TRUSTY_CMD_SHIFT | TRUSTY_RESPONSE_BITMASK | TRUSTY_STOP_BITMASK; let mut buf = Vec::new(); - buf.try_reserve(LEGACY_NON_SEC_RSP_HEADER_SIZE).map_err(|_e| Error::AllocationFailed)?; + buf.try_reserve(LEGACY_NON_SEC_RSP_HEADER_SIZE) + .map_err(|_e| Error::AllocationFailed)?; buf.extend_from_slice(&raw_cmd.to_ne_bytes()); match result { @@ -218,7 +219,8 @@ pub fn serialize_trusty_rsp(rsp: TrustyPerformOpRsp) -> Result, Error> { fn serialize_trusty_raw_rsp(cmd: u32, raw_data: &[u8]) -> Result, Error> { let raw_cmd = cmd << TRUSTY_CMD_SHIFT | TRUSTY_RESPONSE_BITMASK | TRUSTY_STOP_BITMASK; let mut buf = Vec::new(); - buf.try_reserve(CMD_SIZE + raw_data.len()).map_err(|_e| Error::AllocationFailed)?; + buf.try_reserve(CMD_SIZE + raw_data.len()) + .map_err(|_e| Error::AllocationFailed)?; buf.extend_from_slice(&raw_cmd.to_ne_bytes()); buf.extend_from_slice(raw_data); Ok(buf) @@ -359,7 +361,8 @@ impl InnerSerialize for Vec { Ok((buf, &rest[len..])) } fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { - buf.try_reserve(4 + self.len()).map_err(|_e| Error::AllocationFailed)?; + buf.try_reserve(4 + self.len()) + .map_err(|_e| Error::AllocationFailed)?; let len = self.len() as u32; buf.extend_from_slice(&len.to_ne_bytes()); buf.extend_from_slice(self); @@ -370,7 +373,10 @@ impl InnerSerialize for Vec { impl InnerSerialize for KmVersion { fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { let (v, rest) = ::deserialize(data)?; - Ok((Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, rest)) + Ok(( + Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, + rest, + )) } fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { (*self as u32).serialize_into(buf) @@ -380,7 +386,10 @@ impl InnerSerialize for KmVersion { impl InnerSerialize for Algorithm { fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { let (v, rest) = ::deserialize(data)?; - Ok((Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, rest)) + Ok(( + Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, + rest, + )) } fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { (*self as u32).serialize_into(buf) @@ -390,7 +399,10 @@ impl InnerSerialize for Algorithm { impl InnerSerialize for VerifiedBootState { fn deserialize(data: &[u8]) -> Result<(Self, &[u8]), Error> { let (v, rest) = ::deserialize(data)?; - Ok((Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, rest)) + Ok(( + Self::try_from(v as i32).map_err(|_e| Error::InvalidEnumValue(v))?, + rest, + )) } fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { (*self as u32).serialize_into(buf) @@ -474,7 +486,8 @@ impl InnerSerialize for GetAuthTokenKeyResponse { Err(Error::UnexpectedResponse) } fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { - buf.try_reserve(self.key_material.len()).map_err(|_e| Error::AllocationFailed)?; + buf.try_reserve(self.key_material.len()) + .map_err(|_e| Error::AllocationFailed)?; buf.extend_from_slice(&self.key_material); Ok(()) } @@ -496,7 +509,8 @@ impl InnerSerialize for GetDeviceInfoResponse { Err(Error::UnexpectedResponse) } fn serialize_into(&self, buf: &mut Vec) -> Result<(), Error> { - buf.try_reserve(self.device_ids.len()).map_err(|_e| Error::AllocationFailed)?; + buf.try_reserve(self.device_ids.len()) + .map_err(|_e| Error::AllocationFailed)?; buf.extend_from_slice(&self.device_ids); Ok(()) } @@ -766,7 +780,11 @@ mod tests { } #[test] fn test_get_version_serialize() { - let msg = GetVersionResponse { major_ver: 1, minor_ver: 2, subminor_ver: 3 }; + let msg = GetVersionResponse { + major_ver: 1, + minor_ver: 2, + subminor_ver: 3, + }; let data = vec![1, 2, 3]; let mut got_data = Vec::new(); @@ -799,8 +817,9 @@ mod tests { } #[test] fn test_get_uds_certs_rsp_serialize() { - let msg = - TrustyPerformSecureOpRsp::GetUdsCerts(GetUdsCertsResponse { uds_certs: vec![1, 2, 3] }); + let msg = TrustyPerformSecureOpRsp::GetUdsCerts(GetUdsCertsResponse { + uds_certs: vec![1, 2, 3], + }); #[cfg(target_endian = "little")] let data = concat!( /* cmd */ "0b000000", /* rc */ "00000000", /* len */ "03000000", diff --git a/libs/rust/wire/src/lib.rs b/libs/rust/wire/src/lib.rs index 90d3699..c7323ea 100644 --- a/libs/rust/wire/src/lib.rs +++ b/libs/rust/wire/src/lib.rs @@ -475,9 +475,9 @@ impl AsCborValue for String { impl AsCborValue for u64 { fn from_cbor_value(value: cbor::value::Value) -> Result { match value { - cbor::value::Value::Integer(i) => { - i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) - } + cbor::value::Value::Integer(i) => i + .try_into() + .map_err(|_| crate::CborError::OutOfRangeIntegerValue), v => crate::cbor_type_error(&v, "u64"), } } @@ -494,9 +494,9 @@ impl AsCborValue for u64 { impl AsCborValue for i64 { fn from_cbor_value(value: cbor::value::Value) -> Result { match value { - cbor::value::Value::Integer(i) => { - i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) - } + cbor::value::Value::Integer(i) => i + .try_into() + .map_err(|_| crate::CborError::OutOfRangeIntegerValue), v => crate::cbor_type_error(&v, "i64"), } } @@ -513,9 +513,9 @@ impl AsCborValue for i64 { impl AsCborValue for u32 { fn from_cbor_value(value: cbor::value::Value) -> Result { match value { - cbor::value::Value::Integer(i) => { - i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) - } + cbor::value::Value::Integer(i) => i + .try_into() + .map_err(|_| crate::CborError::OutOfRangeIntegerValue), v => crate::cbor_type_error(&v, "u32"), } } @@ -564,9 +564,9 @@ impl AsCborValue for () { impl AsCborValue for i32 { fn from_cbor_value(value: cbor::value::Value) -> Result { match value { - cbor::value::Value::Integer(i) => { - i.try_into().map_err(|_| crate::CborError::OutOfRangeIntegerValue) - } + cbor::value::Value::Integer(i) => i + .try_into() + .map_err(|_| crate::CborError::OutOfRangeIntegerValue), v => crate::cbor_type_error(&v, "i64"), } } diff --git a/libs/rust/wire/src/tests.rs b/libs/rust/wire/src/tests.rs index 3942a6a..c34c6fe 100644 --- a/libs/rust/wire/src/tests.rs +++ b/libs/rust/wire/src/tests.rs @@ -33,12 +33,20 @@ fn test_read_to_value_ok() { fn test_read_to_value_fail() { let tests = vec![ ("0101", CborError::ExtraneousData), - ("43", CborError::DecodeFailed(cbor::de::Error::Io(EndOfFile))), + ( + "43", + CborError::DecodeFailed(cbor::de::Error::Io(EndOfFile)), + ), ("8001", CborError::ExtraneousData), ]; for (hexdata, want_err) in tests { let data = hex::decode(hexdata).unwrap(); let got_err = read_to_value(&data).expect_err("decoding expected to fail"); - assert_eq!(format!("{:?}", got_err), format!("{:?}", want_err), "failed for {}", hexdata); + assert_eq!( + format!("{:?}", got_err), + format!("{:?}", want_err), + "failed for {}", + hexdata + ); } } diff --git a/libs/rust/wire/src/types.rs b/libs/rust/wire/src/types.rs index d78b745..1f74151 100644 --- a/libs/rust/wire/src/types.rs +++ b/libs/rust/wire/src/types.rs @@ -395,6 +395,8 @@ pub struct AttestationIdInfo { pub model: Vec, } +unsafe impl Sync for AttestationIdInfo {} + // Provisioner->TA at device provisioning time. #[derive(Debug, AsCborValue)] pub struct SetAttestationIdsRequest { From e95bf542422e2623e0b5d72c48d212e09674edb0 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Wed, 8 Oct 2025 02:28:39 +0800 Subject: [PATCH 06/46] saving progress Signed-off-by: qwq233 --- libs/rust/src/keymaster/db.rs | 2 +- libs/rust/src/keymaster/keymint_device.rs | 51 +- libs/rust/src/keymaster/keymint_operation.rs | 51 ++ libs/rust/src/keymaster/mod.rs | 1 + libs/rust/src/keymaster/utils.rs | 469 ++++++++++++++++++- libs/rust/src/proto/mod.rs | 2 +- 6 files changed, 548 insertions(+), 28 deletions(-) create mode 100644 libs/rust/src/keymaster/keymint_operation.rs diff --git a/libs/rust/src/keymaster/db.rs b/libs/rust/src/keymaster/db.rs index cab4886..cfa030b 100644 --- a/libs/rust/src/keymaster/db.rs +++ b/libs/rust/src/keymaster/db.rs @@ -1657,7 +1657,7 @@ impl FromSql for EncryptedBy { /// A database representation of wall clock time. DateTime stores unix epoch time as /// i64 in milliseconds. #[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)] -pub struct DateTime(i64); +pub struct DateTime(pub(crate) i64); /// Error type returned when creating DateTime or converting it from and to /// SystemTime. diff --git a/libs/rust/src/keymaster/keymint_device.rs b/libs/rust/src/keymaster/keymint_device.rs index d616b58..49ae77d 100644 --- a/libs/rust/src/keymaster/keymint_device.rs +++ b/libs/rust/src/keymaster/keymint_device.rs @@ -14,8 +14,10 @@ //! Provide the [`KeyMintDevice`] wrapper for operating directly on a KeyMint device. +use std::cell::RefCell; use std::sync::{Arc, OnceLock}; +use crate::android::hardware::security::keymint::KeyParameterValue::KeyParameterValue; use crate::android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, IKeyMintDevice::IKeyMintDevice, IKeyMintOperation::IKeyMintOperation, KeyCharacteristics::KeyCharacteristics, @@ -41,17 +43,18 @@ use crate::{ }, watchdog as wd, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Context, Ok, Result}; use kmr_crypto_boring::ec::BoringEc; use kmr_crypto_boring::hmac::BoringHmac; use kmr_crypto_boring::rng::BoringRng; use kmr_crypto_boring::rsa::BoringRsa; use kmr_ta::device::CsrSigningAlgorithm; use kmr_ta::{HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; -use kmr_wire::keymint::KeyMintHardwareInfo; +use kmr_wire::keymint::{KeyMintHardwareInfo, KeyParam}; use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; +use kmr_wire::*; use log::error; -use rsbinder::Strong; +use rsbinder::{Interface, Strong}; /// Wrapper for operating directly on a KeyMint device. /// These methods often mirror methods in [`crate::security_level`]. However @@ -330,7 +333,7 @@ static mut KM_WRAPPER_STRONGBOX: OnceLock = OnceLock::new(); static mut KM_WRAPPER_TEE: OnceLock = OnceLock::new(); struct KeyMintWrapper { - km: KeyMintTa, + km: RefCell, security_level: SecurityLevel, } @@ -339,7 +342,7 @@ unsafe impl Sync for KeyMintWrapper {} impl KeyMintWrapper { fn new(security_level: SecurityLevel) -> Result { Ok(KeyMintWrapper { - km: init_keymint_ta(security_level)?, + km: RefCell::new(init_keymint_ta(security_level)?), security_level: security_level.clone(), }) } @@ -350,18 +353,30 @@ impl KeyMintWrapper { key_blob: &[u8], params: &[KeyParameter], auth_token: Option<&HardwareAuthToken>, - ) -> Result { - let calling_context = CallingContext::get(); - self.km - .begin( - &calling_context, - purpose, - key_blob, - params, - auth_token, - None, - ) - .map_err(|e| anyhow!(err!("KeyMintWrapper::begin failed: {:?}", e))) + ) -> Result<(), Error> { + let km_params: Result> = params.iter().cloned().map(|p| p.to_km()).collect(); + + let req = PerformOpReq::DeviceBegin(BeginRequest { + purpose: kmr_wire::keymint::KeyPurpose::try_from(purpose.0) + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT))?, + key_blob: key_blob.to_vec(), + params: km_params.map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT))?, + auth_token: auth_token + .map(|at| at.to_km()) + .transpose() + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT))?, + }); + + let result = self.km.borrow_mut().process_req(req); + if let None = result.rsp { + return Err(Error::Km(ErrorCode::UNIMPLEMENTED)); + } + let result: InternalBeginResult = match result.rsp.unwrap() { + PerformOpRsp::DeviceBegin(rsp) => rsp.ret, + _ => return Err(Error::Km(ErrorCode::UNIMPLEMENTED)), + }; + + Err(Error::Km(ErrorCode::UNIMPLEMENTED)) } } @@ -481,6 +496,7 @@ fn get_keymint_device( }; let info = strongbox .km + .borrow_mut() .get_hardware_info() .expect(err!("Failed to get hardware info")); Ok((strongbox, info)) @@ -494,6 +510,7 @@ fn get_keymint_device( }; let info = tee .km + .borrow_mut() .get_hardware_info() .expect(err!("Failed to get hardware info")); Ok((tee, info)) diff --git a/libs/rust/src/keymaster/keymint_operation.rs b/libs/rust/src/keymaster/keymint_operation.rs new file mode 100644 index 0000000..020223a --- /dev/null +++ b/libs/rust/src/keymaster/keymint_operation.rs @@ -0,0 +1,51 @@ +use kmr_wire::keymint::KeyParam; +use rsbinder::Interface; + +use crate::android::hardware::security::keymint::{HardwareAuthToken::HardwareAuthToken, IKeyMintOperation::IKeyMintOperation}; + + +pub struct KeyMintOperation { + pub challenge: i64, + pub params: Vec, + // Extra for internal use: returned by bottom half of KeyMint implementation, used on + // all subsequent operation methods to identify the operation. + pub op_handle: i64, +} +impl Interface for KeyMintOperation {} + + +#[allow(non_snake_case, unused_variables)] +impl IKeyMintOperation for KeyMintOperation { + fn r#updateAad( + &self, + input: &[u8], + authToken: Option<&HardwareAuthToken>, + timeStampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, + ) -> rsbinder::status::Result<()> { + todo!() + } + + fn r#update( + &self, + input: &[u8], + authToken: Option<&HardwareAuthToken>, + timeStampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, + ) -> rsbinder::status::Result> { + todo!() + } + + fn r#finish( + &self, + input: Option<&[u8]>, + signature: Option<&[u8]>, + authToken: Option<&HardwareAuthToken>, + timestampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, + confirmationToken: Option<&[u8]>, + ) -> rsbinder::status::Result> { + todo!() + } + + fn r#abort(&self) -> rsbinder::status::Result<()> { + todo!() + } +} \ No newline at end of file diff --git a/libs/rust/src/keymaster/mod.rs b/libs/rust/src/keymaster/mod.rs index 3e6407a..26b0f98 100644 --- a/libs/rust/src/keymaster/mod.rs +++ b/libs/rust/src/keymaster/mod.rs @@ -7,6 +7,7 @@ pub mod enforcements; pub mod error; pub mod key_parameter; pub mod keymint_device; +pub mod keymint_operation; pub mod permission; pub mod security_level; pub mod super_key; diff --git a/libs/rust/src/keymaster/utils.rs b/libs/rust/src/keymaster/utils.rs index 82e6178..aca1bde 100644 --- a/libs/rust/src/keymaster/utils.rs +++ b/libs/rust/src/keymaster/utils.rs @@ -1,16 +1,14 @@ use crate::{ android::hardware::security::keymint::{ ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, - KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, - }, - consts, err, - keymaster::{ + KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, KeyParameterValue::KeyParameterValue, + }, consts, err, keymaster::{ error::KsError as Error, key_parameter::KeyParameter, keymint_device::KeyMintDevice, - }, - watchdog, + }, watchdog }; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result, Ok}; +use kmr_wire::{keymint::{self, KeyParam}, KeySizeInBits}; pub fn log_params(params: &[KmKeyParameter]) -> Vec { params.iter().cloned().collect::>() @@ -127,10 +125,463 @@ where /// Converts a set of key characteristics from the internal representation into a set of /// Authorizations as they are used to convey key characteristics to the clients of keystore. pub fn key_parameters_to_authorizations( - parameters: Vec, -) -> Vec { + parameters: Vec, +) -> Vec { parameters .into_iter() .map(|p| p.into_authorization()) .collect() } + +impl crate::android::hardware::security::keymint::HardwareAuthToken::HardwareAuthToken { + pub fn to_km(&self) -> Result { + Ok(kmr_wire::keymint::HardwareAuthToken { + challenge: self.challenge, + user_id: self.userId, + authenticator_id: self.authenticatorId, + authenticator_type: kmr_wire::keymint::HardwareAuthenticatorType::try_from(self.authenticatorType.0) + .map_err(|e| anyhow!("Failed to convert authenticator type: {:?}", e))?, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: self.timestamp.milliSeconds, + }, + mac: self.mac.clone(), + }) + } +} + +impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { + pub fn to_km(self) -> Result { + let tag = kmr_wire::keymint::Tag::try_from(self.tag.0).unwrap_or(keymint::Tag::Invalid); + let value = self.value; + + match tag { + keymint::Tag::Invalid => Err(anyhow::anyhow!(err!("Invalid tag"))), + keymint::Tag::Purpose => { + let value = match value { + KeyParameterValue::KeyPurpose(v) => { + Ok(kmr_wire::keymint::KeyPurpose::try_from(v.0) + .map_err(|e| anyhow!("Failed to convert key purpose: {:?}", e))?) + } + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::Purpose(value)) + } + keymint::Tag::Algorithm => { + let value = match value { + KeyParameterValue::Algorithm(v) => { + Ok(kmr_wire::keymint::Algorithm::try_from(v.0) + .map_err(|e| anyhow::anyhow!("Failed to convert algorithm: {:?}", e))?) + } + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::Algorithm(value)) + } + keymint::Tag::KeySize => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::KeySize(KeySizeInBits(value as u32))) + } + keymint::Tag::BlockMode => { + let value = match value { + KeyParameterValue::BlockMode(v) => { + Ok(kmr_wire::keymint::BlockMode::try_from(v.0) + .map_err(|e| anyhow!("Failed to convert block mode: {:?}", e))?) + } + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::BlockMode(value)) + } + keymint::Tag::Digest => { + let value = match value { + KeyParameterValue::Digest(v) => Ok(kmr_wire::keymint::Digest::try_from(v.0) + .map_err(|e| anyhow!("Failed to convert digest: {:?}", e))?), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::Digest(value)) + } + keymint::Tag::Padding => { + let value = match value { + KeyParameterValue::PaddingMode(v) => { + Ok(kmr_wire::keymint::PaddingMode::try_from(v.0) + .map_err(|e| anyhow!("Failed to convert padding mode: {:?}", e))?) + } + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::Padding(value)) + } + keymint::Tag::CallerNonce => Ok(KeyParam::CallerNonce), + keymint::Tag::MinMacLength => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::MinMacLength(value as u32)) + } + keymint::Tag::EcCurve => { + let value = match value { + KeyParameterValue::EcCurve(v) => Ok(kmr_wire::keymint::EcCurve::try_from(v.0) + .map_err(|e| anyhow!("Failed to convert EC curve: {:?}", e))?), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::EcCurve(value)) + } + keymint::Tag::RsaPublicExponent => { + let value = match value { + KeyParameterValue::LongInteger(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::RsaPublicExponent(kmr_wire::RsaExponent(value as u64))) + } + keymint::Tag::IncludeUniqueId => Ok(KeyParam::IncludeUniqueId), + keymint::Tag::RsaOaepMgfDigest => { + let value = match value { + KeyParameterValue::Digest(v) => Ok(kmr_wire::keymint::Digest::try_from(v.0) + .map_err(|e| anyhow!("Failed to convert digest: {:?}", e))?), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::RsaOaepMgfDigest(value)) + } + keymint::Tag::BootloaderOnly => Ok(KeyParam::BootloaderOnly), + keymint::Tag::RollbackResistance => Ok(KeyParam::RollbackResistance), + keymint::Tag::HardwareType => Err(anyhow!(err!("unavailable"))), + keymint::Tag::EarlyBootOnly => Ok(KeyParam::EarlyBootOnly), + keymint::Tag::ActiveDatetime => { + let value = match value { + KeyParameterValue::DateTime(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::ActiveDatetime(kmr_wire::keymint::DateTime { + ms_since_epoch: value, + })) + } + keymint::Tag::OriginationExpireDatetime => { + let value = match value { + KeyParameterValue::DateTime(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::OriginationExpireDatetime( + kmr_wire::keymint::DateTime { + ms_since_epoch: value, + }, + )) + } + keymint::Tag::UsageExpireDatetime => { + let value = match value { + KeyParameterValue::DateTime(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::UsageExpireDatetime(kmr_wire::keymint::DateTime { + ms_since_epoch: value, + })) + } + keymint::Tag::MinSecondsBetweenOps => Err(anyhow!(err!("Not implemented"))), + keymint::Tag::MaxUsesPerBoot => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::MaxUsesPerBoot(value as u32)) + } + keymint::Tag::UsageCountLimit => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::UsageCountLimit(value as u32)) + } + keymint::Tag::UserId => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::UserId(value as u32)) + } + keymint::Tag::UserSecureId => { + let value = match value { + KeyParameterValue::LongInteger(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::UserSecureId(value as u64)) + } + keymint::Tag::NoAuthRequired => Ok(KeyParam::NoAuthRequired), + keymint::Tag::UserAuthType => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::UserAuthType(value as u32)) + } + keymint::Tag::AuthTimeout => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AuthTimeout(value as u32)) + } + keymint::Tag::AllowWhileOnBody => Ok(KeyParam::AllowWhileOnBody), + keymint::Tag::TrustedUserPresenceRequired => Ok(KeyParam::TrustedUserPresenceRequired), + keymint::Tag::TrustedConfirmationRequired => Ok(KeyParam::TrustedConfirmationRequired), + keymint::Tag::UnlockedDeviceRequired => Ok(KeyParam::UnlockedDeviceRequired), + keymint::Tag::ApplicationId => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::ApplicationId(value)) + } + keymint::Tag::ApplicationData => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::ApplicationData(value)) + } + keymint::Tag::CreationDatetime => { + let value = match value { + KeyParameterValue::DateTime(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::CreationDatetime(kmr_wire::keymint::DateTime { + ms_since_epoch: value, + })) + } + keymint::Tag::Origin => { + let value = match value { + KeyParameterValue::Origin(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::Origin( + kmr_wire::keymint::KeyOrigin::try_from(value.0) + .map_err(|e| anyhow!("Failed to convert origin: {:?}", e))?, + )) + } + keymint::Tag::RootOfTrust => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + Ok(KeyParam::RootOfTrust(value)) + } + keymint::Tag::OsVersion => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::OsVersion(value as u32)) + } + keymint::Tag::OsPatchlevel => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::OsPatchlevel(value as u32)) + } + keymint::Tag::UniqueId => Err(anyhow!("Not implemented")), + keymint::Tag::AttestationChallenge => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationChallenge(value)) + } + keymint::Tag::AttestationApplicationId => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationApplicationId(value)) + } + keymint::Tag::AttestationIdBrand => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdBrand(value)) + } + keymint::Tag::AttestationIdDevice => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdDevice(value)) + } + keymint::Tag::AttestationIdProduct => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdProduct(value)) + } + keymint::Tag::AttestationIdSerial => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdSerial(value)) + } + keymint::Tag::AttestationIdImei => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdImei(value)) + } + keymint::Tag::AttestationIdMeid => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdMeid(value)) + } + keymint::Tag::AttestationIdManufacturer => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdManufacturer(value)) + } + keymint::Tag::AttestationIdModel => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdModel(value)) + } + keymint::Tag::VendorPatchlevel => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::VendorPatchlevel(value as u32)) + }, + keymint::Tag::BootPatchlevel => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::BootPatchlevel(value as u32)) + }, + keymint::Tag::DeviceUniqueAttestation => Ok(KeyParam::DeviceUniqueAttestation), + keymint::Tag::IdentityCredentialKey => Err(anyhow!(err!("Not implemented"))), + keymint::Tag::StorageKey => Ok(KeyParam::StorageKey), + keymint::Tag::AttestationIdSecondImei => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::AttestationIdSecondImei(value)) + } + keymint::Tag::AssociatedData => Err(anyhow!(err!("Not implemented"))), + keymint::Tag::Nonce => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::Nonce(value)) + }, + keymint::Tag::MacLength => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::MacLength(value as u32)) + }, + keymint::Tag::ResetSinceIdRotation => Ok(KeyParam::ResetSinceIdRotation), + keymint::Tag::ConfirmationToken => Err(anyhow!(err!("Not implemented"))), + keymint::Tag::CertificateSerial => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::CertificateSerial(value)) + }, + keymint::Tag::CertificateSubject => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::CertificateSubject(value)) + }, + keymint::Tag::CertificateNotBefore => { + let value = match value { + KeyParameterValue::DateTime(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::CertificateNotBefore(kmr_wire::keymint::DateTime { + ms_since_epoch: value, + })) + }, + keymint::Tag::CertificateNotAfter => { + let value = match value { + KeyParameterValue::DateTime(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::CertificateNotAfter(kmr_wire::keymint::DateTime { + ms_since_epoch: value, + })) + }, + keymint::Tag::MaxBootLevel => { + let value = match value { + KeyParameterValue::Integer(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::MaxBootLevel(value as u32)) + }, + keymint::Tag::ModuleHash => { + let value = match value { + KeyParameterValue::Blob(v) => Ok(v), + _ => return Err(anyhow!("Mismatched key parameter value type")), + }?; + + Ok(KeyParam::ModuleHash(value)) + }, + } + } +} diff --git a/libs/rust/src/proto/mod.rs b/libs/rust/src/proto/mod.rs index 30f61eb..e626ba5 100644 --- a/libs/rust/src/proto/mod.rs +++ b/libs/rust/src/proto/mod.rs @@ -1 +1 @@ -pub mod storage; +pub mod storage; \ No newline at end of file From 931ab2520aa86e3e57dcd4409f074592ba3ec0a0 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Thu, 9 Oct 2025 23:11:10 +0800 Subject: [PATCH 07/46] first builtable commit Signed-off-by: qwq233 --- libs/rust/Cargo.toml | 1 + libs/rust/rsproperties-service/Cargo.toml | 3 - .../examples/example_service.rs | 80 --- libs/rust/src/keymaster/error.rs | 20 + libs/rust/src/keymaster/keymint_device.rs | 494 +++++++++++++++--- libs/rust/src/keymaster/keymint_operation.rs | 39 +- libs/rust/src/keymaster/security_level.rs | 26 +- libs/rust/src/keymaster/utils.rs | 26 +- libs/rust/src/lib.rs | 1 + libs/rust/src/main.rs | 1 + 10 files changed, 509 insertions(+), 182 deletions(-) delete mode 100644 libs/rust/rsproperties-service/examples/example_service.rs diff --git a/libs/rust/Cargo.toml b/libs/rust/Cargo.toml index 047dc85..c7bfd06 100644 --- a/libs/rust/Cargo.toml +++ b/libs/rust/Cargo.toml @@ -4,6 +4,7 @@ cargo-features = ["profile-rustflags", "trim-paths"] name = "OhMyKeymint" version = "0.1.1" edition = "2021" +license = "AGPL-3.0-or-later" [[bin]] name = "keymint" diff --git a/libs/rust/rsproperties-service/Cargo.toml b/libs/rust/rsproperties-service/Cargo.toml index 403751a..334ae1f 100644 --- a/libs/rust/rsproperties-service/Cargo.toml +++ b/libs/rust/rsproperties-service/Cargo.toml @@ -16,6 +16,3 @@ env_logger = "*" ctrlc = "*" anyhow = "*" clap = "*" - -[[example]] -name = "example_service" diff --git a/libs/rust/rsproperties-service/examples/example_service.rs b/libs/rust/rsproperties-service/examples/example_service.rs deleted file mode 100644 index b318097..0000000 --- a/libs/rust/rsproperties-service/examples/example_service.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::fs::{create_dir_all, remove_dir_all}; -use std::path::PathBuf; - -use clap::Parser; - -#[derive(Parser, Debug)] -#[command(name = "example_service")] -#[command(about = "Example service for rsproperties-service")] -struct Args { - /// Properties directory path - #[arg(long, help = "Directory path for system properties")] - properties_dir: Option, - - /// Socket directory path - #[arg(long, help = "Directory path for property service sockets")] - socket_dir: Option, -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Parse command line arguments - let args = Args::parse(); - - // Initialize logging - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - - // Setup directories - let properties_dir = args - .properties_dir - .unwrap_or_else(|| PathBuf::from("__properties__")); - let socket_dir = args - .socket_dir - .unwrap_or_else(|| properties_dir.join("sockets")); - - // Clean and create directories - let _ = remove_dir_all(&properties_dir); - let _ = remove_dir_all(&socket_dir); - create_dir_all(&properties_dir)?; - create_dir_all(&socket_dir)?; - - println!("📁 Created directories:"); - println!(" Properties: {properties_dir:?}"); - println!(" Sockets: {socket_dir:?}"); - - // Create PropertyConfig - let config = rsproperties::PropertyConfig::with_both_dirs(properties_dir, socket_dir); - - println!("🚀 Starting rsproperties services..."); - - // Initialize the services - let (socket_service, properties_service) = rsproperties_service::run( - config, - vec![], // property_contexts_files - vec![], // build_prop_files - ) - .await?; - - println!("✅ Services started successfully!"); - println!("🔄 Services are running. Press Ctrl+C to stop."); - - // Handle graceful shutdown - tokio::select! { - _ = tokio::signal::ctrl_c() => { - println!("\n🛑 Shutdown signal received..."); - } - result1 = socket_service.join_handle => { - if let Err(e) = result1 { - eprintln!("❌ Socket service error: {e}"); - } - } - result2 = properties_service.join_handle => { - if let Err(e) = result2 { - eprintln!("❌ Properties service error: {e}"); - } - } - } - - println!("👋 Services stopped."); - Ok(()) -} diff --git a/libs/rust/src/keymaster/error.rs b/libs/rust/src/keymaster/error.rs index 2466b9b..53caa77 100644 --- a/libs/rust/src/keymaster/error.rs +++ b/libs/rust/src/keymaster/error.rs @@ -52,3 +52,23 @@ pub fn map_binder_status(r: rsbinder::status::Result) -> Result Status { + match r { + KsError::Rc(rc) => { + Status::new_service_specific_error(rc.0, format!("KeystoreError: {:?}", rc).into()) + } + KsError::Km(ec) => { + Status::new_service_specific_error(ec.0, format!("KeymintError: {:?}", ec).into()) + } + KsError::Binder(ec, se) => Status::from(ec), + KsError::BinderTransaction(sc) => Status::from(sc), + } +} + +pub fn map_ks_result(r: Result) -> Result { + match r { + Ok(t) => Ok(t), + Err(e) => Err(map_ks_error(e)), + } +} diff --git a/libs/rust/src/keymaster/keymint_device.rs b/libs/rust/src/keymaster/keymint_device.rs index 49ae77d..d6a62ad 100644 --- a/libs/rust/src/keymaster/keymint_device.rs +++ b/libs/rust/src/keymaster/keymint_device.rs @@ -14,10 +14,9 @@ //! Provide the [`KeyMintDevice`] wrapper for operating directly on a KeyMint device. -use std::cell::RefCell; -use std::sync::{Arc, OnceLock}; +use std::sync::OnceLock; -use crate::android::hardware::security::keymint::KeyParameterValue::KeyParameterValue; +use crate::android::hardware::security::keymint::IKeyMintOperation::BnKeyMintOperation; use crate::android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, IKeyMintDevice::IKeyMintDevice, IKeyMintOperation::IKeyMintOperation, KeyCharacteristics::KeyCharacteristics, @@ -28,6 +27,8 @@ use crate::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, }; use crate::global::AID_KEYSTORE; +use crate::keymaster::db::Uuid; +use crate::keymaster::error::{map_binder_status, map_ks_error, map_ks_result}; use crate::keymint::{clock, sdd, soft}; use crate::{ android::hardware::security::keymint::ErrorCode::ErrorCode, @@ -43,18 +44,18 @@ use crate::{ }, watchdog as wd, }; -use anyhow::{anyhow, Context, Ok, Result}; +use anyhow::{Context, Ok, Result}; use kmr_crypto_boring::ec::BoringEc; use kmr_crypto_boring::hmac::BoringHmac; use kmr_crypto_boring::rng::BoringRng; use kmr_crypto_boring::rsa::BoringRsa; use kmr_ta::device::CsrSigningAlgorithm; use kmr_ta::{HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; -use kmr_wire::keymint::{KeyMintHardwareInfo, KeyParam}; +use kmr_wire::keymint::KeyParam; use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; use kmr_wire::*; use log::error; -use rsbinder::{Interface, Strong}; +use rsbinder::{ExceptionCode, Interface, Status, Strong}; /// Wrapper for operating directly on a KeyMint device. /// These methods often mirror methods in [`crate::security_level`]. However @@ -68,6 +69,7 @@ use rsbinder::{Interface, Strong}; pub struct KeyMintDevice { km_dev: KeyMintWrapper, version: i32, + km_uuid: Uuid, security_level: SecurityLevel, } @@ -85,12 +87,16 @@ impl KeyMintDevice { /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] pub fn get(security_level: SecurityLevel) -> Result { - let (km_dev, hw_info) = - get_keymint_device(security_level).context(err!("get_keymint_device failed"))?; + let km_dev = + get_keymint_device(security_level).context(err!("get_keymint_device failed: {:?}", security_level))?; + let hw_info = km_dev.get_hardware_info().unwrap(); + let km_uuid: Uuid = Default::default(); + let wrapper = KeyMintWrapper::new(security_level).unwrap(); Ok(KeyMintDevice { - km_dev, + km_dev: wrapper, version: hw_info.version_number, + km_uuid, security_level: get_keymaster_security_level(hw_info.security_level)?, }) } @@ -127,9 +133,9 @@ impl KeyMintDevice { creator: F, ) -> Result<()> where - F: FnOnce(KeyMintWrapper) -> Result, + F: FnOnce(&dyn IKeyMintDevice) -> Result, { - let creation_result = creator(self.km_dev).context(err!("creator failed"))?; + let creation_result = creator(&self.km_dev).context(err!("creator failed"))?; let key_parameters = crate::keymaster::utils::key_characteristics_to_internal( creation_result.keyCharacteristics, ); @@ -138,6 +144,8 @@ impl KeyMintDevice { let mut key_metadata = KeyMetaData::new(); key_metadata.add(KeyMetaEntry::CreationDate(creation_date)); + let mut blob_metadata = BlobMetaData::new(); + blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); db.store_new_key( key_desc, @@ -146,6 +154,7 @@ impl KeyMintDevice { &BlobInfo::new(&creation_result.keyBlob, &blob_metadata), &CertificateInfo::new(None, None), &key_metadata, + &self.km_uuid, ) .context(err!("store_new_key failed"))?; Ok(()) @@ -176,7 +185,7 @@ impl KeyMintDevice { lookup: Result<(KeyIdGuard, KeyEntry)>, ) -> Result> { match lookup { - Ok(result) => Ok(Some(result)), + Result::Ok(result) => Ok(Some(result)), Err(e) => match e.root_cause().downcast_ref::() { Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None), _ => Err(e), @@ -211,7 +220,13 @@ impl KeyMintDevice { // is considered corrupted and needs to be replaced with a new one. let key_blob = key_entry .take_key_blob_info() - .and_then(|(key_blob, blob_metadata)| Some(key_blob)); + .and_then(|(key_blob, blob_metadata)| { + if Some(&self.km_uuid) == blob_metadata.km_uuid() { + Some(key_blob) + } else { + None + } + }); if let Some(key_blob_vec) = key_blob { let (key_characteristics, key_blob) = self @@ -220,16 +235,18 @@ impl KeyMintDevice { &key_id_guard, KeyBlob::NonSensitive(key_blob_vec), |key_blob| { - let _wp = wd::watch(concat!( - "KeyMintDevice::lookup_or_generate_key: ", - "calling IKeyMintDevice::getKeyCharacteristics." - )); - self.km_dev.getKeyCharacteristics(key_blob, &[], &[]) + map_binder_status({ + let _wp = wd::watch(concat!( + "KeyMintDevice::lookup_or_generate_key: ", + "calling IKeyMintDevice::getKeyCharacteristics." + )); + self.km_dev.getKeyCharacteristics(key_blob, &[], &[]) + }) }, ) .context(err!("calling getKeyCharacteristics"))?; - if validate_characteristics(&key_characteristics) { + if validate_characteristics(&key_characteristics[..]) { return Ok((key_id_guard, key_blob)); } @@ -313,8 +330,9 @@ impl KeyMintDevice { .upgrade_keyblob_if_required_with(db, key_id_guard, key_blob, |blob| { let _wp = wd::watch("KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::begin"); - self.km_dev - .begin(purpose, blob, operation_parameters, auth_token) + let result: std::result::Result = self.km_dev + .begin(purpose, blob, operation_parameters, auth_token); + map_binder_status(result) }) .context(err!("Failed to begin operation."))?; let operation: Strong = begin_result @@ -328,55 +346,388 @@ impl KeyMintDevice { } } +static mut KM_STRONGBOX: OnceLock = OnceLock::new(); + +static mut KM_TEE: OnceLock = OnceLock::new(); + static mut KM_WRAPPER_STRONGBOX: OnceLock = OnceLock::new(); static mut KM_WRAPPER_TEE: OnceLock = OnceLock::new(); -struct KeyMintWrapper { - km: RefCell, +pub struct KeyMintWrapper { security_level: SecurityLevel, } unsafe impl Sync for KeyMintWrapper {} -impl KeyMintWrapper { - fn new(security_level: SecurityLevel) -> Result { - Ok(KeyMintWrapper { - km: RefCell::new(init_keymint_ta(security_level)?), - security_level: security_level.clone(), - }) - } +impl Interface for KeyMintWrapper {} +#[allow(non_snake_case)] +impl IKeyMintDevice for KeyMintWrapper { fn begin( &self, purpose: KeyPurpose, key_blob: &[u8], params: &[KeyParameter], auth_token: Option<&HardwareAuthToken>, - ) -> Result<(), Error> { + ) -> Result { let km_params: Result> = params.iter().cloned().map(|p| p.to_km()).collect(); + let km_params = km_params.map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)); + let km_params = map_ks_result(km_params)?; let req = PerformOpReq::DeviceBegin(BeginRequest { - purpose: kmr_wire::keymint::KeyPurpose::try_from(purpose.0) - .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT))?, + purpose: kmr_wire::keymint::KeyPurpose::try_from(purpose.0).map_err(|_| { + Status::new_service_specific_error(ErrorCode::INVALID_ARGUMENT.0, None) + })?, key_blob: key_blob.to_vec(), - params: km_params.map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT))?, - auth_token: auth_token - .map(|at| at.to_km()) - .transpose() - .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT))?, + params: km_params.clone(), + auth_token: auth_token.map(|at| at.to_km()).transpose().map_err(|_| { + Status::new_service_specific_error(ErrorCode::INVALID_ARGUMENT.0, None) + })?, }); - let result = self.km.borrow_mut().process_req(req); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); if let None = result.rsp { - return Err(Error::Km(ErrorCode::UNIMPLEMENTED)); + return Err(Status::new_service_specific_error(result.error_code, None)); } let result: InternalBeginResult = match result.rsp.unwrap() { PerformOpRsp::DeviceBegin(rsp) => rsp.ret, - _ => return Err(Error::Km(ErrorCode::UNIMPLEMENTED)), + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + let operation = crate::keymaster::keymint_operation::OKeyMintOperation::new( + self.security_level.clone(), + result.challenge, + km_params, + result.op_handle, + ); + let operation = BnKeyMintOperation::new_binder(operation); + + let resp = crate::android::hardware::security::keymint::BeginResult::BeginResult { + operation: Some(operation), + challenge: result.challenge, + params: params.to_vec(), }; - Err(Error::Km(ErrorCode::UNIMPLEMENTED)) + Result::Ok(resp) + } + + fn getHardwareInfo( + &self, + ) -> Result< + crate::android::hardware::security::keymint::KeyMintHardwareInfo::KeyMintHardwareInfo, + Status, + > { + todo!() + } + + fn addRngEntropy(&self, _arg_data: &[u8]) -> rsbinder::status::Result<()> { + todo!() + } + + fn generateKey( + &self, + _arg_keyParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + _arg_attestationKey: Option< + &crate::android::hardware::security::keymint::AttestationKey::AttestationKey, + >, + ) -> rsbinder::status::Result< + crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, + > { + todo!() + } + + fn importKey( + &self, + _arg_keyParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + _arg_keyFormat: crate::android::hardware::security::keymint::KeyFormat::KeyFormat, + _arg_keyData: &[u8], + _arg_attestationKey: Option< + &crate::android::hardware::security::keymint::AttestationKey::AttestationKey, + >, + ) -> rsbinder::status::Result< + crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, + > { + todo!() + } + + fn importWrappedKey( + &self, + _arg_wrappedKeyData: &[u8], + _arg_wrappingKeyBlob: &[u8], + _arg_maskingKey: &[u8], + _arg_unwrappingParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + _arg_passwordSid: i64, + _arg_biometricSid: i64, + ) -> rsbinder::status::Result< + crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, + > { + todo!() + } + + fn upgradeKey( + &self, + _arg_keyBlobToUpgrade: &[u8], + _arg_upgradeParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + ) -> rsbinder::status::Result> { + todo!() + } + + fn deleteKey(&self, _arg_keyBlob: &[u8]) -> rsbinder::status::Result<()> { + todo!() + } + + fn deleteAllKeys(&self) -> rsbinder::status::Result<()> { + todo!() + } + + fn destroyAttestationIds(&self) -> rsbinder::status::Result<()> { + todo!() + } + + fn deviceLocked( + &self, + _arg_passwordOnly: bool, + _arg_timestampToken: Option< + &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, + >, + ) -> rsbinder::status::Result<()> { + todo!() + } + + fn earlyBootEnded(&self) -> rsbinder::status::Result<()> { + todo!() + } + + fn convertStorageKeyToEphemeral( + &self, + _arg_storageKeyBlob: &[u8], + ) -> rsbinder::status::Result> { + todo!() + } + + fn getKeyCharacteristics( + &self, + _arg_keyBlob: &[u8], + _arg_appId: &[u8], + _arg_appData: &[u8], + ) -> rsbinder::status::Result< + Vec, + > { + todo!() + } + + fn getRootOfTrustChallenge(&self) -> rsbinder::status::Result<[u8; 16]> { + todo!() + } + + fn getRootOfTrust(&self, _arg_challenge: &[u8; 16]) -> rsbinder::status::Result> { + todo!() + } + + fn sendRootOfTrust(&self, _arg_rootOfTrust: &[u8]) -> rsbinder::status::Result<()> { + todo!() + } + + fn setAdditionalAttestationInfo( + &self, + _arg_info: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + ) -> rsbinder::status::Result<()> { + todo!() + } +} + +impl KeyMintWrapper { + fn new(security_level: SecurityLevel) -> Result { + Ok(KeyMintWrapper { + security_level: security_level.clone(), + }) + } + + pub fn op_update_aad( + &self, + op_handle: i64, + input: &[u8], + auth_token: Option< + &crate::android::hardware::security::keymint::HardwareAuthToken::HardwareAuthToken, + >, + timestamp_token: Option< + &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, + >, + ) -> Result<(), Error> { + let hardware_auth_token = if let Some(at) = auth_token { + Some(at.to_km()?) + } else { + None + }; + let timestamp_token = if let Some(tt) = timestamp_token { + Some(kmr_wire::secureclock::TimeStampToken { + challenge: tt.challenge, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: tt.timestamp.milliSeconds, + }, + mac: tt.mac.clone(), + }) + } else { + None + }; + + let req = PerformOpReq::OperationUpdateAad(UpdateAadRequest { + op_handle, + input: input.to_vec(), + auth_token: hardware_auth_token, + timestamp_token: timestamp_token, + }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Error::Binder( + ExceptionCode::ServiceSpecific, + result.error_code, + )); + } + let _result: UpdateAadResponse = match result.rsp.unwrap() { + PerformOpRsp::OperationUpdateAad(rsp) => rsp, + _ => return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)), + }; + + Result::Ok(()) + } + + pub fn op_update( + &self, + op_handle: i64, + input: &[u8], + auth_token: Option< + &crate::android::hardware::security::keymint::HardwareAuthToken::HardwareAuthToken, + >, + timestamp_token: Option< + &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, + >, + ) -> Result, Error> { + let hardware_auth_token = if let Some(at) = auth_token { + Some(at.to_km()?) + } else { + None + }; + let timestamp_token = if let Some(tt) = timestamp_token { + Some(kmr_wire::secureclock::TimeStampToken { + challenge: tt.challenge, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: tt.timestamp.milliSeconds, + }, + mac: tt.mac.clone(), + }) + } else { + None + }; + + let req = PerformOpReq::OperationUpdate(UpdateRequest { + op_handle, + input: input.to_vec(), + auth_token: hardware_auth_token, + timestamp_token: timestamp_token, + }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Error::Binder( + ExceptionCode::ServiceSpecific, + result.error_code, + )); + } + let result: UpdateResponse = match result.rsp.unwrap() { + PerformOpRsp::OperationUpdate(rsp) => rsp, + _ => return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)), + }; + + Result::Ok(result.ret) + } + + pub fn op_finish( + &self, + op_handle: i64, + input: Option<&[u8]>, + signature: Option<&[u8]>, + auth_token: Option< + &crate::android::hardware::security::keymint::HardwareAuthToken::HardwareAuthToken, + >, + timestamp_token: Option< + &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, + >, + confirmation_token: Option<&[u8]>, + ) -> Result, Error> { + let hardware_auth_token = if let Some(at) = auth_token { + Some(at.to_km()?) + } else { + None + }; + let timestamp_token = if let Some(tt) = timestamp_token { + Some(kmr_wire::secureclock::TimeStampToken { + challenge: tt.challenge, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: tt.timestamp.milliSeconds, + }, + mac: tt.mac.clone(), + }) + } else { + None + }; + let input = input.map(|i| i.to_vec()); + let signature = signature.map(|s| s.to_vec()); + let confirmation_token = confirmation_token.map(|c| c.to_vec()); + + let req = PerformOpReq::OperationFinish(FinishRequest { + op_handle, + input: input, + signature: signature, + auth_token: hardware_auth_token, + timestamp_token: timestamp_token, + confirmation_token: confirmation_token, + }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Error::Binder( + ExceptionCode::ServiceSpecific, + result.error_code, + )); + } + let result: FinishResponse = match result.rsp.unwrap() { + PerformOpRsp::OperationFinish(rsp) => rsp, + _ => return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)), + }; + + Result::Ok(result.ret) + } + + pub fn op_abort(&self, op_handle: i64) -> Result<(), Error> { + let req = PerformOpReq::OperationAbort(AbortRequest { op_handle }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Error::Binder( + ExceptionCode::ServiceSpecific, + result.error_code, + )); + } + let _result: AbortResponse = match result.rsp.unwrap() { + PerformOpRsp::OperationAbort(rsp) => rsp, + _ => return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)), + }; + + Result::Ok(()) } } @@ -427,7 +778,7 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { let sdd_mgr: Option> = match sdd::HostSddManager::new(&mut rng) { - Ok(v) => Some(Box::new(v)), + Result::Ok(v) => Some(Box::new(v)), Err(e) => { error!("Failed to initialize secure deletion data manager: {:?}", e); None @@ -483,37 +834,52 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { Ok(KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev)) } -fn get_keymint_device( - security_level: SecurityLevel, -) -> Result<(KeyMintWrapper, KeyMintHardwareInfo)> { +pub fn get_keymint_device<'a>(security_level: SecurityLevel) -> Result<&'a mut KeyMintTa> { match security_level { SecurityLevel::STRONGBOX => { let strongbox = unsafe { - *KM_WRAPPER_STRONGBOX.get_or_init(|| { - KeyMintWrapper::new(SecurityLevel::STRONGBOX) - .expect(err!("Failed to init strongbox wrapper")) + KM_STRONGBOX.get_mut_or_init(|| { + init_keymint_ta(security_level).expect(err!("Failed to init strongbox wrapper").as_str()) }) }; - let info = strongbox - .km - .borrow_mut() - .get_hardware_info() - .expect(err!("Failed to get hardware info")); - Ok((strongbox, info)) + + Ok(strongbox) } SecurityLevel::TRUSTED_ENVIRONMENT => { let tee = unsafe { - *KM_WRAPPER_TEE.get_or_init(|| { - KeyMintWrapper::new(SecurityLevel::TRUSTED_ENVIRONMENT) - .expect(err!("Failed to init tee wrapper")) + KM_TEE.get_mut_or_init(|| { + init_keymint_ta(security_level).expect(err!("Failed to init tee wrapper").as_str()) + }) + }; + + Ok(tee) + } + SecurityLevel::SOFTWARE => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context(err!("Software KeyMint not supported")), + _ => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context(err!("Unknown security level")), + } +} + +pub fn get_keymint_wrapper<'a>(security_level: SecurityLevel) -> Result<&'a mut KeyMintWrapper> { + match security_level { + SecurityLevel::STRONGBOX => { + let wrapper = unsafe { + KM_WRAPPER_STRONGBOX.get_mut_or_init(|| { + KeyMintWrapper::new(security_level) + .expect(err!("Failed to init strongbox wrapper").as_str()) + }) + }; + Ok(wrapper) + } + SecurityLevel::TRUSTED_ENVIRONMENT => { + let wrapper = unsafe { + KM_WRAPPER_TEE.get_mut_or_init(|| { + KeyMintWrapper::new(security_level) + .expect(err!("Failed to init tee wrapper").as_str()) }) }; - let info = tee - .km - .borrow_mut() - .get_hardware_info() - .expect(err!("Failed to get hardware info")); - Ok((tee, info)) + Ok(wrapper) } SecurityLevel::SOFTWARE => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) .context(err!("Software KeyMint not supported")), diff --git a/libs/rust/src/keymaster/keymint_operation.rs b/libs/rust/src/keymaster/keymint_operation.rs index 020223a..8f3b3da 100644 --- a/libs/rust/src/keymaster/keymint_operation.rs +++ b/libs/rust/src/keymaster/keymint_operation.rs @@ -1,28 +1,43 @@ use kmr_wire::keymint::KeyParam; use rsbinder::Interface; -use crate::android::hardware::security::keymint::{HardwareAuthToken::HardwareAuthToken, IKeyMintOperation::IKeyMintOperation}; +use crate::{android::hardware::security::keymint::{HardwareAuthToken::HardwareAuthToken, IKeyMintOperation::IKeyMintOperation, SecurityLevel::SecurityLevel}, keymaster::{error::map_ks_error, keymint_device::{KeyMintWrapper, get_keymint_wrapper}}}; -pub struct KeyMintOperation { +pub struct OKeyMintOperation { + security_level: SecurityLevel, pub challenge: i64, pub params: Vec, // Extra for internal use: returned by bottom half of KeyMint implementation, used on // all subsequent operation methods to identify the operation. pub op_handle: i64, } -impl Interface for KeyMintOperation {} +impl Interface for OKeyMintOperation { +} + +impl OKeyMintOperation { + pub fn new(security_level: SecurityLevel, challenge: i64, params: Vec, op_handle: i64) -> Self { + OKeyMintOperation { + security_level, + challenge, + params, + op_handle, + } + } +} #[allow(non_snake_case, unused_variables)] -impl IKeyMintOperation for KeyMintOperation { +impl IKeyMintOperation for OKeyMintOperation { fn r#updateAad( &self, input: &[u8], authToken: Option<&HardwareAuthToken>, timeStampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, ) -> rsbinder::status::Result<()> { - todo!() + get_keymint_wrapper(self.security_level).unwrap().op_update_aad(self.op_handle, input, authToken, timeStampToken) + .map_err(|e| map_ks_error(e))?; + Ok(()) } fn r#update( @@ -31,7 +46,9 @@ impl IKeyMintOperation for KeyMintOperation { authToken: Option<&HardwareAuthToken>, timeStampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, ) -> rsbinder::status::Result> { - todo!() + get_keymint_wrapper(self.security_level).unwrap().op_update(self.op_handle, input, authToken, timeStampToken) + .map_err(|e| map_ks_error(e)) + .and_then(|rsp: Vec| {Ok(rsp.to_vec())}) } fn r#finish( @@ -42,10 +59,14 @@ impl IKeyMintOperation for KeyMintOperation { timestampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, confirmationToken: Option<&[u8]>, ) -> rsbinder::status::Result> { - todo!() + get_keymint_wrapper(self.security_level).unwrap().op_finish(self.op_handle, input, signature, authToken, timestampToken, confirmationToken) + .map_err(|e| map_ks_error(e)) + .and_then(|rsp: Vec| {Ok(rsp.to_vec())}) } fn r#abort(&self) -> rsbinder::status::Result<()> { - todo!() + get_keymint_wrapper(self.security_level).unwrap().op_abort(self.op_handle) + .map_err(|e| map_ks_error(e)) } -} \ No newline at end of file +} + diff --git a/libs/rust/src/keymaster/security_level.rs b/libs/rust/src/keymaster/security_level.rs index 8ddc49e..f7f52f4 100644 --- a/libs/rust/src/keymaster/security_level.rs +++ b/libs/rust/src/keymaster/security_level.rs @@ -3,9 +3,7 @@ use std::time::SystemTime; use crate::{ android::{ hardware::security::keymint::{ - Algorithm::Algorithm, AttestationKey::AttestationKey, ErrorCode::ErrorCode, - KeyCreationResult::KeyCreationResult, KeyParameter::KeyParameter, - KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, + Algorithm::Algorithm, AttestationKey::AttestationKey, ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, KeyCreationResult::KeyCreationResult, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag }, system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, @@ -15,13 +13,13 @@ use crate::{ err, global::{DB, SUPER_KEY, UNDEFINED_NOT_AFTER}, keymaster::{ - attestation_key_utils::{get_attest_key_info, AttestationKeyInfo}, + attestation_key_utils::{AttestationKeyInfo, get_attest_key_info}, db::{ BlobInfo, BlobMetaEntry, CertificateInfo, DateTime, DateTimeError, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, Uuid, }, - error::KsError, - keymint_device::KeyMintDevice, + error::{KsError, map_binder_status}, + keymint_device::{KeyMintDevice, KeyMintWrapper, get_keymint_wrapper}, super_key::{KeyBlob, SuperKeyManager}, utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, }, @@ -34,27 +32,26 @@ use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; use kmr_ta::HardwareInfo; -use rsbinder::thread_state::CallingContext; +use rsbinder::{Strong, thread_state::CallingContext}; -pub struct KeystoreSecurityLevel { +pub struct KeystoreSecurityLevel<'a> { security_level: SecurityLevel, hw_info: HardwareInfo, km_uuid: Uuid, - keymint: KeyMintDevice, + keymint: &'a dyn IKeyMintDevice, } -impl KeystoreSecurityLevel { +impl<'a> KeystoreSecurityLevel<'a> { pub fn new( security_level: SecurityLevel, hw_info: HardwareInfo, km_uuid: Uuid, - keymint: KeyMintDevice, ) -> Self { KeystoreSecurityLevel { security_level, hw_info, km_uuid, - keymint, + keymint: get_keymint_wrapper(security_level).unwrap(), } } @@ -211,7 +208,7 @@ impl KeystoreSecurityLevel { F: Fn(&[u8]) -> Result, { let (v, upgraded_blob) = crate::keymaster::utils::upgrade_keyblob_if_required_with( - &*self.keymint, + get_keymint_wrapper(self.security_level).unwrap(), self.hw_info.version_number, key_blob, params, @@ -422,7 +419,8 @@ impl KeystoreSecurityLevel { ), 5000, // Generate can take a little longer. ); - self.keymint.generateKey(¶ms, attest_key.as_ref()) + let result = self.keymint.generateKey(¶ms, attest_key.as_ref()); + map_binder_status(result) }, ) .context(err!( diff --git a/libs/rust/src/keymaster/utils.rs b/libs/rust/src/keymaster/utils.rs index aca1bde..5006e51 100644 --- a/libs/rust/src/keymaster/utils.rs +++ b/libs/rust/src/keymaster/utils.rs @@ -134,18 +134,20 @@ pub fn key_parameters_to_authorizations( } impl crate::android::hardware::security::keymint::HardwareAuthToken::HardwareAuthToken { - pub fn to_km(&self) -> Result { - Ok(kmr_wire::keymint::HardwareAuthToken { - challenge: self.challenge, - user_id: self.userId, - authenticator_id: self.authenticatorId, - authenticator_type: kmr_wire::keymint::HardwareAuthenticatorType::try_from(self.authenticatorType.0) - .map_err(|e| anyhow!("Failed to convert authenticator type: {:?}", e))?, - timestamp: kmr_wire::secureclock::Timestamp { - milliseconds: self.timestamp.milliSeconds, - }, - mac: self.mac.clone(), - }) + pub fn to_km(&self) -> Result { + core::result::Result::Ok( + kmr_wire::keymint::HardwareAuthToken { + challenge: self.challenge, + user_id: self.userId, + authenticator_id: self.authenticatorId, + authenticator_type: kmr_wire::keymint::HardwareAuthenticatorType::try_from(self.authenticatorType.0) + .map_err(|_| Error::Km(crate::android::hardware::security::keymint::ErrorCode::ErrorCode::INVALID_ARGUMENT))?, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: self.timestamp.milliSeconds, + }, + mac: self.mac.clone(), + } + ) } } diff --git a/libs/rust/src/lib.rs b/libs/rust/src/lib.rs index 82eb304..bf747e5 100644 --- a/libs/rust/src/lib.rs +++ b/libs/rust/src/lib.rs @@ -1,4 +1,5 @@ #![recursion_limit = "256"] +#![feature(once_cell_get_mut)] use anyhow::Context; use lazy_static; diff --git a/libs/rust/src/main.rs b/libs/rust/src/main.rs index c12a4d2..5a249d7 100644 --- a/libs/rust/src/main.rs +++ b/libs/rust/src/main.rs @@ -1,4 +1,5 @@ #![recursion_limit = "256"] +#![feature(once_cell_get_mut)] use std::io::Read; From 88c1e9761d54f56212d255907bbba7509a0ca6ce Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 11 Oct 2025 00:16:10 +0800 Subject: [PATCH 08/46] saving progress Signed-off-by: qwq233 --- libs/rust/Cargo.toml | 8 +- .../android/security/metrics/Algorithm.aidl | 40 + .../aidl/android/security/metrics/AtomID.aidl | 35 + .../android/security/metrics/CrashStats.aidl | 23 + .../android/security/metrics/EcCurve.aidl | 33 + .../metrics/HardwareAuthenticatorType.aidl | 32 + .../security/metrics/IKeystoreMetrics.aidl | 42 + .../metrics/KeyCreationWithAuthInfo.aidl | 35 + .../metrics/KeyCreationWithGeneralInfo.aidl | 35 + .../KeyCreationWithPurposeAndModesInfo.aidl | 32 + .../metrics/KeyOperationWithGeneralInfo.aidl | 32 + .../KeyOperationWithPurposeAndModesInfo.aidl | 31 + .../android/security/metrics/KeyOrigin.aidl | 43 + .../metrics/Keystore2AtomWithOverflow.aidl | 34 + .../security/metrics/KeystoreAtom.aidl | 32 + .../security/metrics/KeystoreAtomPayload.aidl | 41 + .../android/security/metrics/Outcome.aidl | 32 + .../android/security/metrics/Purpose.aidl | 54 + .../android/security/metrics/RkpError.aidl | 32 + .../security/metrics/RkpErrorStats.aidl | 29 + .../security/metrics/SecurityLevel.aidl | 31 + .../android/security/metrics/Storage.aidl | 42 + .../security/metrics/StorageStats.aidl | 30 + libs/rust/boringssl/Cargo.toml | 3 + libs/rust/boringssl/src/aes_cmac.rs | 4 +- libs/rust/build.rs | 1 + libs/rust/src/keymaster/db.rs | 134 ++- libs/rust/src/keymaster/error.rs | 68 ++ libs/rust/src/keymaster/keymint_device.rs | 18 +- libs/rust/src/keymaster/keymint_operation.rs | 72 +- libs/rust/src/keymaster/metrics_store.rs | 1007 +++++++++++++++++ libs/rust/src/keymaster/mod.rs | 2 + libs/rust/src/keymaster/operation.rs | 871 ++++++++++++++ libs/rust/src/keymaster/security_level.rs | 574 +++++++++- libs/rust/src/keymaster/super_key.rs | 2 +- libs/rust/src/keymaster/utils.rs | 49 +- libs/rust/src/keymint/sdd.rs | 1 + libs/rust/src/lib.rs | 12 +- libs/rust/src/logging.rs | 5 +- libs/rust/src/main.rs | 9 +- libs/rust/src/plat/property_watcher.rs | 11 +- 41 files changed, 3529 insertions(+), 92 deletions(-) create mode 100644 libs/rust/aidl/android/security/metrics/Algorithm.aidl create mode 100644 libs/rust/aidl/android/security/metrics/AtomID.aidl create mode 100644 libs/rust/aidl/android/security/metrics/CrashStats.aidl create mode 100644 libs/rust/aidl/android/security/metrics/EcCurve.aidl create mode 100644 libs/rust/aidl/android/security/metrics/HardwareAuthenticatorType.aidl create mode 100644 libs/rust/aidl/android/security/metrics/IKeystoreMetrics.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeyOrigin.aidl create mode 100644 libs/rust/aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeystoreAtom.aidl create mode 100644 libs/rust/aidl/android/security/metrics/KeystoreAtomPayload.aidl create mode 100644 libs/rust/aidl/android/security/metrics/Outcome.aidl create mode 100644 libs/rust/aidl/android/security/metrics/Purpose.aidl create mode 100644 libs/rust/aidl/android/security/metrics/RkpError.aidl create mode 100644 libs/rust/aidl/android/security/metrics/RkpErrorStats.aidl create mode 100644 libs/rust/aidl/android/security/metrics/SecurityLevel.aidl create mode 100644 libs/rust/aidl/android/security/metrics/Storage.aidl create mode 100644 libs/rust/aidl/android/security/metrics/StorageStats.aidl create mode 100644 libs/rust/src/keymaster/metrics_store.rs create mode 100644 libs/rust/src/keymaster/operation.rs diff --git a/libs/rust/Cargo.toml b/libs/rust/Cargo.toml index c7bfd06..2b94116 100644 --- a/libs/rust/Cargo.toml +++ b/libs/rust/Cargo.toml @@ -10,10 +10,10 @@ license = "AGPL-3.0-or-later" name = "keymint" path = "src/main.rs" -[lib] -name = "ohmykeymint" -path = "src/lib.rs" -crate-type = ["cdylib"] +# [lib] +# name = "ohmykeymint" +# path = "src/lib.rs" +# crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/libs/rust/aidl/android/security/metrics/Algorithm.aidl b/libs/rust/aidl/android/security/metrics/Algorithm.aidl new file mode 100644 index 0000000..8e8d107 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/Algorithm.aidl @@ -0,0 +1,40 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * Algorithm enum as defined in stats/enums/system/security/keystore2/enums.proto. + * @hide + */ +@Backing(type="int") +enum Algorithm { + /** ALGORITHM is prepended because UNSPECIFIED exists in other enums as well. */ + ALGORITHM_UNSPECIFIED = 0, + + /** Asymmetric algorithms. */ + RSA = 1, + + /** 2 removed, do not reuse. */ + EC = 3, + + /** Block cipher algorithms. */ + AES = 32, + TRIPLE_DES = 33, + + /** MAC algorithms. */ + HMAC = 128, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/AtomID.aidl b/libs/rust/aidl/android/security/metrics/AtomID.aidl new file mode 100644 index 0000000..3043ed3 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/AtomID.aidl @@ -0,0 +1,35 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * Atom IDs as defined in frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum AtomID { + STORAGE_STATS = 10103, + // reserved 10104 + KEY_CREATION_WITH_GENERAL_INFO = 10118, + KEY_CREATION_WITH_AUTH_INFO = 10119, + KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO = 10120, + KEYSTORE2_ATOM_WITH_OVERFLOW = 10121, + KEY_OPERATION_WITH_PURPOSE_AND_MODES_INFO = 10122, + KEY_OPERATION_WITH_GENERAL_INFO = 10123, + RKP_ERROR_STATS = 10124, + CRASH_STATS = 10125, +} diff --git a/libs/rust/aidl/android/security/metrics/CrashStats.aidl b/libs/rust/aidl/android/security/metrics/CrashStats.aidl new file mode 100644 index 0000000..8ca043b --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/CrashStats.aidl @@ -0,0 +1,23 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** @hide */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable CrashStats { + int count_of_crash_events; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/EcCurve.aidl b/libs/rust/aidl/android/security/metrics/EcCurve.aidl new file mode 100644 index 0000000..7b1a5a2 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/EcCurve.aidl @@ -0,0 +1,33 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * EcCurve enum as defined in Keystore2KeyCreationWithGeneralInfo of + * frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum EcCurve { + /** Unspecified takes 0. Other values are incremented by 1 compared to the keymint spec. */ + EC_CURVE_UNSPECIFIED = 0, + P_224 = 1, + P_256 = 2, + P_384 = 3, + P_521 = 4, + CURVE_25519 = 5, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/HardwareAuthenticatorType.aidl b/libs/rust/aidl/android/security/metrics/HardwareAuthenticatorType.aidl new file mode 100644 index 0000000..b13f6ea --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/HardwareAuthenticatorType.aidl @@ -0,0 +1,32 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * HardwareAuthenticatorType enum as defined in Keystore2KeyCreationWithAuthInfo of + * frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum HardwareAuthenticatorType { + /** Unspecified takes 0. Other values are incremented by 1 compared to keymint spec. */ + AUTH_TYPE_UNSPECIFIED = 0, + NONE = 1, + PASSWORD = 2, + FINGERPRINT = 3, + ANY = 5, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/IKeystoreMetrics.aidl b/libs/rust/aidl/android/security/metrics/IKeystoreMetrics.aidl new file mode 100644 index 0000000..342cf01 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/IKeystoreMetrics.aidl @@ -0,0 +1,42 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.KeystoreAtom; +import android.security.metrics.AtomID; + +/** + * IKeystoreMetrics interface exposes the method for system server to pull metrics from keystore. + * @hide + */ +interface IKeystoreMetrics { + /** + * Allows the metrics routing proxy to pull the metrics from keystore. + * + * @return an array of KeystoreAtom objects with the atomID. There can be multiple atom objects + * for the same atomID, encapsulating different combinations of values for the atom fields. + * If there is no atom object found for the atomID in the metrics store, an empty array is + * returned. + * + * Callers require 'PullMetrics' permission. + * + * @param atomID - ID of the atom to be pulled. + * + * Errors are reported as service specific errors. + */ + KeystoreAtom[] pullMetrics(in AtomID atomID); +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl b/libs/rust/aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl new file mode 100644 index 0000000..ff200bc --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl @@ -0,0 +1,35 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.HardwareAuthenticatorType; +import android.security.metrics.SecurityLevel; + +/** + * Atom that encapsulates authentication related information in key creation events. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeyCreationWithAuthInfo { + HardwareAuthenticatorType user_auth_type; + /** + * Base 10 logarithm of time out in seconds. + * Logarithm is taken in order to reduce the cardinaltiy. + */ + int log10_auth_key_timeout_seconds; + SecurityLevel security_level; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl b/libs/rust/aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl new file mode 100644 index 0000000..74cd9ef --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl @@ -0,0 +1,35 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.Algorithm; +import android.security.metrics.EcCurve; +import android.security.metrics.KeyOrigin; + +/** + * Atom that encapsulates a set of general information in key creation events. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeyCreationWithGeneralInfo { + Algorithm algorithm; + int key_size; + EcCurve ec_curve; + KeyOrigin key_origin; + int error_code; + boolean attestation_requested = false; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl b/libs/rust/aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl new file mode 100644 index 0000000..dda61c4 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl @@ -0,0 +1,32 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.Algorithm; + +/** + * Atom that encapsulates the repeated fields in key creation events. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeyCreationWithPurposeAndModesInfo { + Algorithm algorithm; + int purpose_bitmap; + int padding_mode_bitmap; + int digest_bitmap; + int block_mode_bitmap; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl b/libs/rust/aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl new file mode 100644 index 0000000..d70aaf3 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl @@ -0,0 +1,32 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.Outcome; +import android.security.metrics.SecurityLevel; + +/** + * Atom that encapsulates a set of general information in key operation events. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeyOperationWithGeneralInfo { + Outcome outcome; + int error_code; + boolean key_upgraded; + SecurityLevel security_level; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl b/libs/rust/aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl new file mode 100644 index 0000000..e3769e1 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.Purpose; + +/** + * Atom that encapsulates the purpose, padding mode, digest and block mode fields in key operations. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeyOperationWithPurposeAndModesInfo { + Purpose purpose; + int padding_mode_bitmap; + int digest_bitmap; + int block_mode_bitmap; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/KeyOrigin.aidl b/libs/rust/aidl/android/security/metrics/KeyOrigin.aidl new file mode 100644 index 0000000..b472bc3 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeyOrigin.aidl @@ -0,0 +1,43 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * KeyOrigin enum as defined in Keystore2KeyCreationWithGeneralInfo of + * frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum KeyOrigin { + /** Unspecified takes 0. Other values are incremented by 1 compared to keymint spec. */ + ORIGIN_UNSPECIFIED = 0, + + /** Generated in KeyMint. Should not exist outside the TEE. */ + GENERATED = 1, + + /** Derived inside KeyMint. Likely exists off-device. */ + DERIVED = 2, + + /** Imported into KeyMint. Existed as cleartext in Android. */ + IMPORTED = 3, + + /** Previously used for another purpose that is now obsolete. */ + RESERVED = 4, + + /** Securely imported into KeyMint. */ + SECURELY_IMPORTED = 5, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl b/libs/rust/aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl new file mode 100644 index 0000000..f2ac399 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl @@ -0,0 +1,34 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.AtomID; + +/** + * Logs the atom id of the atoms associated with key creation/operation events, that have reached + * the maximum storage limit allocated for different atom objects of that atom, + * in keystore in-memory store. + * + * Size of the storage bucket for each atom is limited considering their expected cardinaltity. + * This limit may exceed if the dimensions of the atoms take a large number of unexpected + * combinations. This atom is used to track such cases. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable Keystore2AtomWithOverflow { + AtomID atom_id; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/KeystoreAtom.aidl b/libs/rust/aidl/android/security/metrics/KeystoreAtom.aidl new file mode 100644 index 0000000..843e80b --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeystoreAtom.aidl @@ -0,0 +1,32 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.KeystoreAtomPayload; + +/** + * Encapsulates a particular atom object of type KeystoreAtomPayload its count. Note that + * the field: count is only relevant for the atom types that are stored in the + * in-memory metrics store. E.g. count field is not relevant for the atom types such as StorageStats + * that are not stored in the metrics store. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable KeystoreAtom { + KeystoreAtomPayload payload; + int count; +} diff --git a/libs/rust/aidl/android/security/metrics/KeystoreAtomPayload.aidl b/libs/rust/aidl/android/security/metrics/KeystoreAtomPayload.aidl new file mode 100644 index 0000000..2f89a2d --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/KeystoreAtomPayload.aidl @@ -0,0 +1,41 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.KeyCreationWithGeneralInfo; +import android.security.metrics.KeyCreationWithPurposeAndModesInfo; +import android.security.metrics.KeyCreationWithAuthInfo; +import android.security.metrics.KeyOperationWithGeneralInfo; +import android.security.metrics.KeyOperationWithPurposeAndModesInfo; +import android.security.metrics.StorageStats; +import android.security.metrics.Keystore2AtomWithOverflow; +import android.security.metrics.RkpErrorStats; +import android.security.metrics.CrashStats; + +/** @hide */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +union KeystoreAtomPayload { + StorageStats storageStats; + KeyCreationWithGeneralInfo keyCreationWithGeneralInfo; + KeyCreationWithAuthInfo keyCreationWithAuthInfo; + KeyCreationWithPurposeAndModesInfo keyCreationWithPurposeAndModesInfo; + Keystore2AtomWithOverflow keystore2AtomWithOverflow; + KeyOperationWithPurposeAndModesInfo keyOperationWithPurposeAndModesInfo; + KeyOperationWithGeneralInfo keyOperationWithGeneralInfo; + RkpErrorStats rkpErrorStats; + CrashStats crashStats; +} diff --git a/libs/rust/aidl/android/security/metrics/Outcome.aidl b/libs/rust/aidl/android/security/metrics/Outcome.aidl new file mode 100644 index 0000000..006548b --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/Outcome.aidl @@ -0,0 +1,32 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * Outcome enum as defined in Keystore2KeyOperationWithGeneralInfo of + * frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum Outcome { + OUTCOME_UNSPECIFIED = 0, + DROPPED = 1, + SUCCESS = 2, + ABORT = 3, + PRUNED = 4, + ERROR = 5, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/Purpose.aidl b/libs/rust/aidl/android/security/metrics/Purpose.aidl new file mode 100644 index 0000000..f003cea --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/Purpose.aidl @@ -0,0 +1,54 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * Purpose enum as defined in Keystore2KeyOperationWithPurposeAndModesInfo of + * frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum Purpose { + /** Unspecified takes 0. Other values are incremented by 1 compared to keymint spec. */ + KEY_PURPOSE_UNSPECIFIED = 0, + + /** Usable with RSA, 3DES and AES keys. */ + ENCRYPT = 1, + + /** Usable with RSA, 3DES and AES keys. */ + DECRYPT = 2, + + /** Usable with RSA, EC and HMAC keys. */ + SIGN = 3, + + /** Usable with RSA, EC and HMAC keys. */ + VERIFY = 4, + + /** 4 is reserved */ + + /** Usable with RSA keys. */ + WRAP_KEY = 6, + + /** Key Agreement, usable with EC keys. */ + AGREE_KEY = 7, + + /** + * Usable as an attestation signing key. Keys with this purpose must not have any other + * purpose. + */ + ATTEST_KEY = 8, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/RkpError.aidl b/libs/rust/aidl/android/security/metrics/RkpError.aidl new file mode 100644 index 0000000..c33703d --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/RkpError.aidl @@ -0,0 +1,32 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * KeyOrigin enum as defined in RkpErrorStats of frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum RkpError { + RKP_ERROR_UNSPECIFIED = 0, + + /** The key pool is out of keys. */ + OUT_OF_KEYS = 1, + + /** Falling back to factory provisioned keys during hybrid mode. */ + FALL_BACK_DURING_HYBRID = 2, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/RkpErrorStats.aidl b/libs/rust/aidl/android/security/metrics/RkpErrorStats.aidl new file mode 100644 index 0000000..dcd5122 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/RkpErrorStats.aidl @@ -0,0 +1,29 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.RkpError; +import android.security.metrics.SecurityLevel; +/** + * Atom that encapsulates error information in remote key provisioning events. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable RkpErrorStats { + RkpError rkpError; + SecurityLevel security_level; +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/SecurityLevel.aidl b/libs/rust/aidl/android/security/metrics/SecurityLevel.aidl new file mode 100644 index 0000000..f627be2 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/SecurityLevel.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * SecurityLevel enum as defined in stats/enums/system/security/keystore2/enums.proto. + * @hide + */ +@Backing(type="int") +enum SecurityLevel { + /** Unspecified takes 0. Other values are incremented by 1 compared to keymint spec. */ + SECURITY_LEVEL_UNSPECIFIED = 0, + SECURITY_LEVEL_SOFTWARE = 1, + SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 2, + SECURITY_LEVEL_STRONGBOX = 3, + SECURITY_LEVEL_KEYSTORE = 4, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/Storage.aidl b/libs/rust/aidl/android/security/metrics/Storage.aidl new file mode 100644 index 0000000..1ba6e1f --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/Storage.aidl @@ -0,0 +1,42 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +/** + * Storage enum as defined in Keystore2StorageStats of frameworks/proto_logging/stats/atoms.proto. + * @hide + */ +@Backing(type="int") +enum Storage { + STORAGE_UNSPECIFIED = 0, + KEY_ENTRY = 1, + KEY_ENTRY_ID_INDEX = 2, + KEY_ENTRY_DOMAIN_NAMESPACE_INDEX = 3, + BLOB_ENTRY = 4, + BLOB_ENTRY_KEY_ENTRY_ID_INDEX = 5, + KEY_PARAMETER = 6, + KEY_PARAMETER_KEY_ENTRY_ID_INDEX = 7, + KEY_METADATA = 8, + KEY_METADATA_KEY_ENTRY_ID_INDEX = 9, + GRANT = 10, + AUTH_TOKEN = 11, + BLOB_METADATA = 12, + BLOB_METADATA_BLOB_ENTRY_ID_INDEX =13, + METADATA = 14, + DATABASE = 15, + LEGACY_STORAGE = 16, +} \ No newline at end of file diff --git a/libs/rust/aidl/android/security/metrics/StorageStats.aidl b/libs/rust/aidl/android/security/metrics/StorageStats.aidl new file mode 100644 index 0000000..6822e86 --- /dev/null +++ b/libs/rust/aidl/android/security/metrics/StorageStats.aidl @@ -0,0 +1,30 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.metrics; + +import android.security.metrics.Storage; + +/** + * Atom that encapsulates a set of general information in key creation events. + * @hide + */ +@RustDerive(Clone=true, Eq=true, PartialEq=true, Ord=true, PartialOrd=true, Hash=true) +parcelable StorageStats { + Storage storage_type; + int size; + int unused_size; +} \ No newline at end of file diff --git a/libs/rust/boringssl/Cargo.toml b/libs/rust/boringssl/Cargo.toml index 5438c06..8e413ff 100644 --- a/libs/rust/boringssl/Cargo.toml +++ b/libs/rust/boringssl/Cargo.toml @@ -20,6 +20,9 @@ anyhow = "*" thiserror = "*" nix = "*" +[target.'cfg(android)'.dependencies] +boringssl = "0.0.5" + [dev-dependencies] kmr-tests = "*" diff --git a/libs/rust/boringssl/src/aes_cmac.rs b/libs/rust/boringssl/src/aes_cmac.rs index c0263ef..063ae39 100644 --- a/libs/rust/boringssl/src/aes_cmac.rs +++ b/libs/rust/boringssl/src/aes_cmac.rs @@ -15,8 +15,8 @@ //! BoringSSL-based implementation of AES-CMAC. use crate::types::CmacCtx; use crate::{malloc_err, openssl_last_err}; -#[cfg(target_os = "android")] -use bssl_sys as ffi; +// #[cfg(target_os = "android")] +// use boringssl::ffi as ffi; use ffi; use kmr_common::{crypto, crypto::OpaqueOr, explicit, km_err, vec_try, Error}; use log::error; diff --git a/libs/rust/build.rs b/libs/rust/build.rs index 09440d8..f33d1e9 100644 --- a/libs/rust/build.rs +++ b/libs/rust/build.rs @@ -31,6 +31,7 @@ fn main() { let dirs = vec![ "aidl/android/system/keystore2", "aidl/android/hardware/security/keymint", + "aidl/android/security/metrics", ]; for dir in dirs { println!("Processing AIDL files in directory: {}", dir); diff --git a/libs/rust/src/keymaster/db.rs b/libs/rust/src/keymaster/db.rs index cfa030b..3ffbe58 100644 --- a/libs/rust/src/keymaster/db.rs +++ b/libs/rust/src/keymaster/db.rs @@ -9,13 +9,11 @@ use std::{ use anyhow::{anyhow, Context, Result}; use rand::random; use rusqlite::{ - params, - types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef}, - Connection, OptionalExtension, ToSql, Transaction, + Connection, OptionalExtension, ToSql, Transaction, params, params_from_iter, types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef} }; use crate::{ - android::hardware::security::keymint::ErrorCode::ErrorCode, + android::{hardware::security::keymint::ErrorCode::ErrorCode, security::metrics::{Storage::Storage as MetricsStorage, StorageStats::StorageStats}}, keymaster::{database::perboot::PerbootDB, super_key::SuperKeyType}, plat::utils::AID_USER_OFFSET, watchdog as wd, @@ -23,6 +21,7 @@ use crate::{ use TransactionBehavior::Immediate; +#[cfg(not(target_os = "android"))] const DB_ROOT_PATH: &str = "./omk/data"; #[cfg(target_os = "android")] @@ -62,8 +61,7 @@ use crate::{ android::{ hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, - HardwareAuthenticatorType::HardwareAuthenticatorType, - KeyParameter::KeyParameter as KmKeyParameter, SecurityLevel::SecurityLevel, Tag::Tag, + HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel, Tag::Tag, }, system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, @@ -77,7 +75,6 @@ use crate::{ permission::KeyPermSet, }, utils::get_current_time_in_milliseconds, - watchdog, }; pub struct KeymasterDb { @@ -1245,6 +1242,127 @@ impl KeymasterDb { .insert_auth_token_entry(AuthTokenEntry::new(auth_token.clone(), BootTime::now())) } + /// Load descriptor of a key by key id + pub fn load_key_descriptor(&mut self, key_id: i64) -> Result> { + let _wp = wd::watch("KeystoreDB::load_key_descriptor"); + + self.with_transaction(TransactionBehavior::Deferred, |tx| { + tx.query_row( + "SELECT domain, namespace, alias FROM persistent.keyentry WHERE id = ?;", + params![key_id], + |row| { + Ok(KeyDescriptor { + domain: Domain(row.get(0)?), + nspace: row.get(1)?, + alias: row.get(2)?, + blob: None, + }) + }, + ) + .optional() + .context("Trying to load key descriptor") + }) + .context(err!()) + } + + fn do_table_size_query( + &mut self, + storage_type: MetricsStorage, + query: &str, + params: &[&str], + ) -> Result { + let (total, unused) = self.with_transaction(TransactionBehavior::Deferred, |tx| { + tx.query_row(query, params_from_iter(params), |row| Ok((row.get(0)?, row.get(1)?))) + .with_context(|| { + err!("get_storage_stat: Error size of storage type {}", storage_type.0) + }) + })?; + Ok(StorageStats { storage_type, size: total, unused_size: unused }) + } + + fn get_total_size(&mut self) -> Result { + self.do_table_size_query( + MetricsStorage::DATABASE, + "SELECT page_count * page_size, freelist_count * page_size + FROM pragma_page_count('persistent'), + pragma_page_size('persistent'), + persistent.pragma_freelist_count();", + &[], + ) + } + + fn get_table_size( + &mut self, + storage_type: MetricsStorage, + schema: &str, + table: &str, + ) -> Result { + self.do_table_size_query( + storage_type, + "SELECT pgsize,unused FROM dbstat(?1) + WHERE name=?2 AND aggregate=TRUE;", + &[schema, table], + ) + } + + + /// Fetches a storage statistics atom for a given storage type. For storage + /// types that map to a table, information about the table's storage is + /// returned. Requests for storage types that are not DB tables return None. + pub fn get_storage_stat(&mut self, storage_type: MetricsStorage) -> Result { + let _wp = wd::watch_millis_with("KeystoreDB::get_storage_stat", 500, storage_type); + + match storage_type { + MetricsStorage::DATABASE => self.get_total_size(), + MetricsStorage::KEY_ENTRY => { + self.get_table_size(storage_type, "persistent", "keyentry") + } + MetricsStorage::KEY_ENTRY_ID_INDEX => { + self.get_table_size(storage_type, "persistent", "keyentry_id_index") + } + MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX => { + self.get_table_size(storage_type, "persistent", "keyentry_domain_namespace_index") + } + MetricsStorage::BLOB_ENTRY => { + self.get_table_size(storage_type, "persistent", "blobentry") + } + MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX => { + self.get_table_size(storage_type, "persistent", "blobentry_keyentryid_index") + } + MetricsStorage::KEY_PARAMETER => { + self.get_table_size(storage_type, "persistent", "keyparameter") + } + MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX => { + self.get_table_size(storage_type, "persistent", "keyparameter_keyentryid_index") + } + MetricsStorage::KEY_METADATA => { + self.get_table_size(storage_type, "persistent", "keymetadata") + } + MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX => { + self.get_table_size(storage_type, "persistent", "keymetadata_keyentryid_index") + } + MetricsStorage::GRANT => self.get_table_size(storage_type, "persistent", "grant"), + MetricsStorage::AUTH_TOKEN => { + // Since the table is actually a BTreeMap now, unused_size is not meaningfully + // reportable + // Size provided is only an approximation + Ok(StorageStats { + storage_type, + size: (self.perboot.auth_tokens_len() * std::mem::size_of::()) + as i32, + unused_size: 0, + }) + } + MetricsStorage::BLOB_METADATA => { + self.get_table_size(storage_type, "persistent", "blobmetadata") + } + MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX => { + self.get_table_size(storage_type, "persistent", "blobmetadata_blobentryid_index") + } + _ => Err(anyhow::Error::msg(format!("Unsupported storage type: {}", storage_type.0))), + } + } + /// Decrements the usage count of a limited use key. This function first checks whether the /// usage has been exhausted, if not, decreases the usage count. If the usage count reaches /// zero, the key also gets marked unreferenced and scheduled for deletion. @@ -1637,7 +1755,7 @@ pub enum EncryptedBy { } impl ToSql for EncryptedBy { - fn to_sql(&self) -> rusqlite::Result { + fn to_sql(&self) -> rusqlite::Result> { match self { Self::Password => Ok(ToSqlOutput::Owned(Value::Null)), Self::KeyId(id) => id.to_sql(), diff --git a/libs/rust/src/keymaster/error.rs b/libs/rust/src/keymaster/error.rs index 53caa77..a15ea69 100644 --- a/libs/rust/src/keymaster/error.rs +++ b/libs/rust/src/keymaster/error.rs @@ -72,3 +72,71 @@ pub fn map_ks_result(r: Result) -> Result { Err(e) => Err(map_ks_error(e)), } } + +/// Convert an [`anyhow::Error`] to a [`binder::Status`], logging the value +/// along the way (except if it is `KEY_NOT_FOUND`). +pub fn into_logged_binder(e: anyhow::Error) -> Status { + // Log everything except key not found. + if !matches!( + e.root_cause().downcast_ref::(), + Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) + ) { + log::error!("{:?}", e); + } + into_binder(e) +} + +/// This function turns an anyhow error into an optional CString. +/// This is especially useful to add a message string to a service specific error. +/// If the formatted string was not convertible because it contained a nul byte, +/// None is returned and a warning is logged. +pub fn anyhow_error_to_string(e: &anyhow::Error) -> Option { + let formatted = format!("{:?}", e); + if formatted.contains('\0') { + log::warn!("Cannot convert error message to String. It contained a nul byte."); + None + } else { + Some(formatted) + } +} + +/// This type is used to send error codes on the wire. +/// +/// Errors are squashed into one number space using following rules: +/// - All Keystore and Keymint errors codes are identity mapped. It's possible because by +/// convention Keystore `ResponseCode` errors are positive, and Keymint `ErrorCode` errors are +/// negative. +/// - `selinux::Error::PermissionDenied` is mapped to `ResponseCode::PERMISSION_DENIED`. +/// - All other error conditions, e.g. Binder errors, are mapped to `ResponseCode::SYSTEM_ERROR`. +/// +/// The type should be used to forward all error codes to clients of Keystore AIDL interface and to +/// metrics events. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct SerializedError(pub i32); + +/// Returns a SerializedError given a reference to Error. +pub fn error_to_serialized_error(e: &KsError) -> SerializedError { + match e { + KsError::Rc(rcode) => SerializedError(rcode.0), + KsError::Km(ec) => SerializedError(ec.0), + // Binder errors are reported as system error. + KsError::Binder(_, _) | KsError::BinderTransaction(_) => { + SerializedError(ResponseCode::SYSTEM_ERROR.0) + } + } +} + +/// Returns a SerializedError given a reference to anyhow::Error. +pub fn anyhow_error_to_serialized_error(e: &anyhow::Error) -> SerializedError { + let root_cause = e.root_cause(); + match root_cause.downcast_ref::() { + Some(e) => error_to_serialized_error(e), + None => SerializedError(ResponseCode::SYSTEM_ERROR.0), + } +} + +/// Convert an [`anyhow::Error`] to a [`binder::Status`]. +pub fn into_binder(e: anyhow::Error) -> rsbinder::Status { + let rc = anyhow_error_to_serialized_error(&e); + rsbinder::Status::new_service_specific_error(rc.0, anyhow_error_to_string(&e)) +} diff --git a/libs/rust/src/keymaster/keymint_device.rs b/libs/rust/src/keymaster/keymint_device.rs index d6a62ad..dade1d4 100644 --- a/libs/rust/src/keymaster/keymint_device.rs +++ b/libs/rust/src/keymaster/keymint_device.rs @@ -87,8 +87,8 @@ impl KeyMintDevice { /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] pub fn get(security_level: SecurityLevel) -> Result { - let km_dev = - get_keymint_device(security_level).context(err!("get_keymint_device failed: {:?}", security_level))?; + let km_dev = get_keymint_device(security_level) + .context(err!("get_keymint_device failed: {:?}", security_level))?; let hw_info = km_dev.get_hardware_info().unwrap(); let km_uuid: Uuid = Default::default(); @@ -330,7 +330,11 @@ impl KeyMintDevice { .upgrade_keyblob_if_required_with(db, key_id_guard, key_blob, |blob| { let _wp = wd::watch("KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::begin"); - let result: std::result::Result = self.km_dev + let result: std::result::Result< + crate::android::hardware::security::keymint::BeginResult::BeginResult, + Status, + > = self + .km_dev .begin(purpose, blob, operation_parameters, auth_token); map_binder_status(result) }) @@ -402,7 +406,7 @@ impl IKeyMintDevice for KeyMintWrapper { } }; - let operation = crate::keymaster::keymint_operation::OKeyMintOperation::new( + let operation = crate::keymaster::keymint_operation::KeyMintOperation::new( self.security_level.clone(), result.challenge, km_params, @@ -839,7 +843,8 @@ pub fn get_keymint_device<'a>(security_level: SecurityLevel) -> Result<&'a mut K SecurityLevel::STRONGBOX => { let strongbox = unsafe { KM_STRONGBOX.get_mut_or_init(|| { - init_keymint_ta(security_level).expect(err!("Failed to init strongbox wrapper").as_str()) + init_keymint_ta(security_level) + .expect(err!("Failed to init strongbox wrapper").as_str()) }) }; @@ -848,7 +853,8 @@ pub fn get_keymint_device<'a>(security_level: SecurityLevel) -> Result<&'a mut K SecurityLevel::TRUSTED_ENVIRONMENT => { let tee = unsafe { KM_TEE.get_mut_or_init(|| { - init_keymint_ta(security_level).expect(err!("Failed to init tee wrapper").as_str()) + init_keymint_ta(security_level) + .expect(err!("Failed to init tee wrapper").as_str()) }) }; diff --git a/libs/rust/src/keymaster/keymint_operation.rs b/libs/rust/src/keymaster/keymint_operation.rs index 8f3b3da..0c737fa 100644 --- a/libs/rust/src/keymaster/keymint_operation.rs +++ b/libs/rust/src/keymaster/keymint_operation.rs @@ -1,10 +1,18 @@ use kmr_wire::keymint::KeyParam; -use rsbinder::Interface; +use rsbinder::{Interface, Strong}; -use crate::{android::hardware::security::keymint::{HardwareAuthToken::HardwareAuthToken, IKeyMintOperation::IKeyMintOperation, SecurityLevel::SecurityLevel}, keymaster::{error::map_ks_error, keymint_device::{KeyMintWrapper, get_keymint_wrapper}}}; +use crate::{ + android::hardware::security::keymint::{ + HardwareAuthToken::HardwareAuthToken, IKeyMintOperation::IKeyMintOperation, + SecurityLevel::SecurityLevel, + }, + keymaster::{ + error::map_ks_error, + keymint_device::{get_keymint_wrapper, KeyMintWrapper}, + }, +}; - -pub struct OKeyMintOperation { +pub struct KeyMintOperation { security_level: SecurityLevel, pub challenge: i64, pub params: Vec, @@ -13,12 +21,16 @@ pub struct OKeyMintOperation { pub op_handle: i64, } -impl Interface for OKeyMintOperation { -} +impl Interface for KeyMintOperation {} -impl OKeyMintOperation { - pub fn new(security_level: SecurityLevel, challenge: i64, params: Vec, op_handle: i64) -> Self { - OKeyMintOperation { +impl KeyMintOperation { + pub fn new( + security_level: SecurityLevel, + challenge: i64, + params: Vec, + op_handle: i64, + ) -> Self { + KeyMintOperation { security_level, challenge, params, @@ -28,14 +40,18 @@ impl OKeyMintOperation { } #[allow(non_snake_case, unused_variables)] -impl IKeyMintOperation for OKeyMintOperation { +impl IKeyMintOperation for KeyMintOperation { fn r#updateAad( &self, input: &[u8], authToken: Option<&HardwareAuthToken>, - timeStampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, + timeStampToken: Option< + &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, + >, ) -> rsbinder::status::Result<()> { - get_keymint_wrapper(self.security_level).unwrap().op_update_aad(self.op_handle, input, authToken, timeStampToken) + get_keymint_wrapper(self.security_level) + .unwrap() + .op_update_aad(self.op_handle, input, authToken, timeStampToken) .map_err(|e| map_ks_error(e))?; Ok(()) } @@ -44,11 +60,15 @@ impl IKeyMintOperation for OKeyMintOperation { &self, input: &[u8], authToken: Option<&HardwareAuthToken>, - timeStampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, + timeStampToken: Option< + &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, + >, ) -> rsbinder::status::Result> { - get_keymint_wrapper(self.security_level).unwrap().op_update(self.op_handle, input, authToken, timeStampToken) + get_keymint_wrapper(self.security_level) + .unwrap() + .op_update(self.op_handle, input, authToken, timeStampToken) .map_err(|e| map_ks_error(e)) - .and_then(|rsp: Vec| {Ok(rsp.to_vec())}) + .and_then(|rsp: Vec| Ok(rsp.to_vec())) } fn r#finish( @@ -56,17 +76,29 @@ impl IKeyMintOperation for OKeyMintOperation { input: Option<&[u8]>, signature: Option<&[u8]>, authToken: Option<&HardwareAuthToken>, - timestampToken: Option<&crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken>, + timestampToken: Option< + &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, + >, confirmationToken: Option<&[u8]>, ) -> rsbinder::status::Result> { - get_keymint_wrapper(self.security_level).unwrap().op_finish(self.op_handle, input, signature, authToken, timestampToken, confirmationToken) + get_keymint_wrapper(self.security_level) + .unwrap() + .op_finish( + self.op_handle, + input, + signature, + authToken, + timestampToken, + confirmationToken, + ) .map_err(|e| map_ks_error(e)) - .and_then(|rsp: Vec| {Ok(rsp.to_vec())}) + .and_then(|rsp: Vec| Ok(rsp.to_vec())) } fn r#abort(&self) -> rsbinder::status::Result<()> { - get_keymint_wrapper(self.security_level).unwrap().op_abort(self.op_handle) + get_keymint_wrapper(self.security_level) + .unwrap() + .op_abort(self.op_handle) .map_err(|e| map_ks_error(e)) } } - diff --git a/libs/rust/src/keymaster/metrics_store.rs b/libs/rust/src/keymaster/metrics_store.rs new file mode 100644 index 0000000..389ffb1 --- /dev/null +++ b/libs/rust/src/keymaster/metrics_store.rs @@ -0,0 +1,1007 @@ +// Copyright 2021, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This is the metrics store module of keystore. It does the following tasks: +//! 1. Processes the data about keystore events asynchronously, and +//! stores them in an in-memory store. +//! 2. Returns the collected metrics when requested by the statsd proxy. + +use crate::keymaster::error::anyhow_error_to_serialized_error; +use crate::global::DB; +use crate::keymaster::key_parameter::KeyParameterValue as KsKeyParamValue; +use crate::err; +use crate::keymaster::operation::Outcome; +use crate::watchdog as wd; +use crate::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin, + KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, + SecurityLevel::SecurityLevel, +}; +use crate::android::security::metrics::{ + Algorithm::Algorithm as MetricsAlgorithm, AtomID::AtomID, CrashStats::CrashStats, + EcCurve::EcCurve as MetricsEcCurve, + HardwareAuthenticatorType::HardwareAuthenticatorType as MetricsHardwareAuthenticatorType, + KeyCreationWithAuthInfo::KeyCreationWithAuthInfo, + KeyCreationWithGeneralInfo::KeyCreationWithGeneralInfo, + KeyCreationWithPurposeAndModesInfo::KeyCreationWithPurposeAndModesInfo, + KeyOperationWithGeneralInfo::KeyOperationWithGeneralInfo, + KeyOperationWithPurposeAndModesInfo::KeyOperationWithPurposeAndModesInfo, + KeyOrigin::KeyOrigin as MetricsKeyOrigin, Keystore2AtomWithOverflow::Keystore2AtomWithOverflow, + KeystoreAtom::KeystoreAtom, KeystoreAtomPayload::KeystoreAtomPayload, + Outcome::Outcome as MetricsOutcome, Purpose::Purpose as MetricsPurpose, + RkpError::RkpError as MetricsRkpError, RkpErrorStats::RkpErrorStats, + SecurityLevel::SecurityLevel as MetricsSecurityLevel, Storage::Storage as MetricsStorage, +}; +use anyhow::{anyhow, Context, Result}; +use std::collections::HashMap; +use std::sync::{LazyLock, Mutex}; + +// Note: Crash events are recorded at keystore restarts, based on the assumption that keystore only +// gets restarted after a crash, during a boot cycle. +const KEYSTORE_CRASH_COUNT_PROPERTY: &str = "keystore.omk.crash_count"; + +/// Singleton for MetricsStore. +pub static METRICS_STORE: LazyLock = LazyLock::new(Default::default); + +/// MetricsStore stores the as in the inner hash map, +/// indexed by the atom id, in the outer hash map. +/// There can be different atom objects with the same atom id based on the values assigned to the +/// fields of the atom objects. When an atom object with a particular combination of field values is +/// inserted, we first check if that atom object is in the inner hash map. If one exists, count +/// is inceremented. Otherwise, the atom object is inserted with count = 1. Note that count field +/// of the atom object itself is set to 0 while the object is stored in the hash map. When the atom +/// objects are queried by the atom id, the corresponding atom objects are retrieved, cloned, and +/// the count field of the cloned objects is set to the corresponding value field in the inner hash +/// map before the query result is returned. +#[derive(Default)] +pub struct MetricsStore { + metrics_store: Mutex>>, +} + +impl std::fmt::Debug for MetricsStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let store = self.metrics_store.lock().unwrap(); + let mut atom_ids: Vec<&AtomID> = store.keys().collect(); + atom_ids.sort(); + for atom_id in atom_ids { + writeln!(f, " {} : [", atom_id.show())?; + let data = store.get(atom_id).unwrap(); + let mut payloads: Vec<&KeystoreAtomPayload> = data.keys().collect(); + payloads.sort(); + for payload in payloads { + let count = data.get(payload).unwrap(); + writeln!(f, " {} => count={count}", payload.show())?; + } + writeln!(f, " ]")?; + } + Ok(()) + } +} + +impl MetricsStore { + /// There are some atoms whose maximum cardinality exceeds the cardinality limits tolerated + /// by statsd. Statsd tolerates cardinality between 200-300. Therefore, the in-memory storage + /// limit for a single atom is set to 250. If the number of atom objects created for a + /// particular atom exceeds this limit, an overflow atom object is created to track the ID of + /// such atoms. + const SINGLE_ATOM_STORE_MAX_SIZE: usize = 250; + + /// Return a vector of atom objects with the given atom ID, if one exists in the metrics_store. + /// If any atom object does not exist in the metrics_store for the given atom ID, return an + /// empty vector. + pub fn get_atoms(&self, atom_id: AtomID) -> Result> { + // StorageStats is an original pulled atom (i.e. not a pushed atom converted to a + // pulled atom). Therefore, it is handled separately. + if AtomID::STORAGE_STATS == atom_id { + let _wp = wd::watch("MetricsStore::get_atoms calling pull_storage_stats"); + return pull_storage_stats(); + } + + // Process keystore crash stats. + if AtomID::CRASH_STATS == atom_id { + let _wp = wd::watch("MetricsStore::get_atoms calling read_keystore_crash_count"); + return match read_keystore_crash_count()? { + Some(count) => Ok(vec![KeystoreAtom { + payload: KeystoreAtomPayload::CrashStats(CrashStats { + count_of_crash_events: count, + }), + ..Default::default() + }]), + None => Err(anyhow!("Crash count property is not set")), + }; + } + + let metrics_store_guard = self.metrics_store.lock().unwrap(); + metrics_store_guard.get(&atom_id).map_or(Ok(Vec::::new()), |atom_count_map| { + Ok(atom_count_map + .iter() + .map(|(atom, count)| KeystoreAtom { payload: atom.clone(), count: *count }) + .collect()) + }) + } + + /// Insert an atom object to the metrics_store indexed by the atom ID. + fn insert_atom(&self, atom_id: AtomID, atom: KeystoreAtomPayload) { + let mut metrics_store_guard = self.metrics_store.lock().unwrap(); + let atom_count_map = metrics_store_guard.entry(atom_id).or_default(); + if atom_count_map.len() < MetricsStore::SINGLE_ATOM_STORE_MAX_SIZE { + let atom_count = atom_count_map.entry(atom).or_insert(0); + *atom_count += 1; + } else { + // Insert an overflow atom + let overflow_atom_count_map = + metrics_store_guard.entry(AtomID::KEYSTORE2_ATOM_WITH_OVERFLOW).or_default(); + + if overflow_atom_count_map.len() < MetricsStore::SINGLE_ATOM_STORE_MAX_SIZE { + let overflow_atom = Keystore2AtomWithOverflow { atom_id }; + let atom_count = overflow_atom_count_map + .entry(KeystoreAtomPayload::Keystore2AtomWithOverflow(overflow_atom)) + .or_insert(0); + *atom_count += 1; + } else { + // This is a rare case, if at all. + log::error!("In insert_atom: Maximum storage limit reached for overflow atom.") + } + } + } +} + +/// Log key creation events to be sent to statsd. +pub fn log_key_creation_event_stats( + sec_level: SecurityLevel, + key_params: &[KeyParameter], + result: &Result, +) { + let ( + key_creation_with_general_info, + key_creation_with_auth_info, + key_creation_with_purpose_and_modes_info, + ) = process_key_creation_event_stats(sec_level, key_params, result); + + METRICS_STORE + .insert_atom(AtomID::KEY_CREATION_WITH_GENERAL_INFO, key_creation_with_general_info); + METRICS_STORE.insert_atom(AtomID::KEY_CREATION_WITH_AUTH_INFO, key_creation_with_auth_info); + METRICS_STORE.insert_atom( + AtomID::KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO, + key_creation_with_purpose_and_modes_info, + ); +} + +// Process the statistics related to key creations and return the three atom objects related to key +// creations: i) KeyCreationWithGeneralInfo ii) KeyCreationWithAuthInfo +// iii) KeyCreationWithPurposeAndModesInfo +fn process_key_creation_event_stats( + sec_level: SecurityLevel, + key_params: &[KeyParameter], + result: &Result, +) -> (KeystoreAtomPayload, KeystoreAtomPayload, KeystoreAtomPayload) { + // In the default atom objects, fields represented by bitmaps and i32 fields + // will take 0, except error_code which defaults to 1 indicating NO_ERROR and key_size, + // and auth_time_out which defaults to -1. + // The boolean fields are set to false by default. + // Some keymint enums do have 0 as an enum variant value. In such cases, the corresponding + // enum variant value in atoms.proto is incremented by 1, in order to have 0 as the reserved + // value for unspecified fields. + let mut key_creation_with_general_info = KeyCreationWithGeneralInfo { + algorithm: MetricsAlgorithm::ALGORITHM_UNSPECIFIED, + key_size: -1, + ec_curve: MetricsEcCurve::EC_CURVE_UNSPECIFIED, + key_origin: MetricsKeyOrigin::ORIGIN_UNSPECIFIED, + error_code: 1, + // Default for bool is false (for attestation_requested field). + ..Default::default() + }; + + let mut key_creation_with_auth_info = KeyCreationWithAuthInfo { + user_auth_type: MetricsHardwareAuthenticatorType::AUTH_TYPE_UNSPECIFIED, + log10_auth_key_timeout_seconds: -1, + security_level: MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED, + }; + + let mut key_creation_with_purpose_and_modes_info = KeyCreationWithPurposeAndModesInfo { + algorithm: MetricsAlgorithm::ALGORITHM_UNSPECIFIED, + // Default for i32 is 0 (for the remaining bitmap fields). + ..Default::default() + }; + + if let Err(ref e) = result { + key_creation_with_general_info.error_code = anyhow_error_to_serialized_error(e).0; + } + + key_creation_with_auth_info.security_level = process_security_level(sec_level); + + for key_param in key_params.iter().map(KsKeyParamValue::from) { + match key_param { + KsKeyParamValue::Algorithm(a) => { + let algorithm = match a { + Algorithm::RSA => MetricsAlgorithm::RSA, + Algorithm::EC => MetricsAlgorithm::EC, + Algorithm::AES => MetricsAlgorithm::AES, + Algorithm::TRIPLE_DES => MetricsAlgorithm::TRIPLE_DES, + Algorithm::HMAC => MetricsAlgorithm::HMAC, + _ => MetricsAlgorithm::ALGORITHM_UNSPECIFIED, + }; + key_creation_with_general_info.algorithm = algorithm; + key_creation_with_purpose_and_modes_info.algorithm = algorithm; + } + KsKeyParamValue::KeySize(s) => { + key_creation_with_general_info.key_size = s; + } + KsKeyParamValue::KeyOrigin(o) => { + key_creation_with_general_info.key_origin = match o { + KeyOrigin::GENERATED => MetricsKeyOrigin::GENERATED, + KeyOrigin::DERIVED => MetricsKeyOrigin::DERIVED, + KeyOrigin::IMPORTED => MetricsKeyOrigin::IMPORTED, + KeyOrigin::RESERVED => MetricsKeyOrigin::RESERVED, + KeyOrigin::SECURELY_IMPORTED => MetricsKeyOrigin::SECURELY_IMPORTED, + _ => MetricsKeyOrigin::ORIGIN_UNSPECIFIED, + } + } + KsKeyParamValue::HardwareAuthenticatorType(a) => { + key_creation_with_auth_info.user_auth_type = match a { + HardwareAuthenticatorType::NONE => MetricsHardwareAuthenticatorType::NONE, + HardwareAuthenticatorType::PASSWORD => { + MetricsHardwareAuthenticatorType::PASSWORD + } + HardwareAuthenticatorType::FINGERPRINT => { + MetricsHardwareAuthenticatorType::FINGERPRINT + } + HardwareAuthenticatorType::ANY => MetricsHardwareAuthenticatorType::ANY, + _ => MetricsHardwareAuthenticatorType::AUTH_TYPE_UNSPECIFIED, + } + } + KsKeyParamValue::AuthTimeout(t) => { + key_creation_with_auth_info.log10_auth_key_timeout_seconds = + f32::log10(t as f32) as i32; + } + KsKeyParamValue::PaddingMode(p) => { + compute_padding_mode_bitmap( + &mut key_creation_with_purpose_and_modes_info.padding_mode_bitmap, + p, + ); + } + KsKeyParamValue::Digest(d) => { + // key_creation_with_purpose_and_modes_info.digest_bitmap = + compute_digest_bitmap( + &mut key_creation_with_purpose_and_modes_info.digest_bitmap, + d, + ); + } + KsKeyParamValue::BlockMode(b) => { + compute_block_mode_bitmap( + &mut key_creation_with_purpose_and_modes_info.block_mode_bitmap, + b, + ); + } + KsKeyParamValue::KeyPurpose(k) => { + compute_purpose_bitmap( + &mut key_creation_with_purpose_and_modes_info.purpose_bitmap, + k, + ); + } + KsKeyParamValue::EcCurve(e) => { + key_creation_with_general_info.ec_curve = match e { + EcCurve::P_224 => MetricsEcCurve::P_224, + EcCurve::P_256 => MetricsEcCurve::P_256, + EcCurve::P_384 => MetricsEcCurve::P_384, + EcCurve::P_521 => MetricsEcCurve::P_521, + EcCurve::CURVE_25519 => MetricsEcCurve::CURVE_25519, + _ => MetricsEcCurve::EC_CURVE_UNSPECIFIED, + } + } + KsKeyParamValue::AttestationChallenge(_) => { + key_creation_with_general_info.attestation_requested = true; + } + _ => {} + } + } + if key_creation_with_general_info.algorithm == MetricsAlgorithm::EC { + // Do not record key sizes if Algorithm = EC, in order to reduce cardinality. + key_creation_with_general_info.key_size = -1; + } + + ( + KeystoreAtomPayload::KeyCreationWithGeneralInfo(key_creation_with_general_info), + KeystoreAtomPayload::KeyCreationWithAuthInfo(key_creation_with_auth_info), + KeystoreAtomPayload::KeyCreationWithPurposeAndModesInfo( + key_creation_with_purpose_and_modes_info, + ), + ) +} + +/// Log key operation events to be sent to statsd. +pub fn log_key_operation_event_stats( + sec_level: SecurityLevel, + key_purpose: KeyPurpose, + op_params: &[KeyParameter], + op_outcome: &Outcome, + key_upgraded: bool, +) { + let (key_operation_with_general_info, key_operation_with_purpose_and_modes_info) = + process_key_operation_event_stats( + sec_level, + key_purpose, + op_params, + op_outcome, + key_upgraded, + ); + METRICS_STORE + .insert_atom(AtomID::KEY_OPERATION_WITH_GENERAL_INFO, key_operation_with_general_info); + METRICS_STORE.insert_atom( + AtomID::KEY_OPERATION_WITH_PURPOSE_AND_MODES_INFO, + key_operation_with_purpose_and_modes_info, + ); +} + +// Process the statistics related to key operations and return the two atom objects related to key +// operations: i) KeyOperationWithGeneralInfo ii) KeyOperationWithPurposeAndModesInfo +fn process_key_operation_event_stats( + sec_level: SecurityLevel, + key_purpose: KeyPurpose, + op_params: &[KeyParameter], + op_outcome: &Outcome, + key_upgraded: bool, +) -> (KeystoreAtomPayload, KeystoreAtomPayload) { + let mut key_operation_with_general_info = KeyOperationWithGeneralInfo { + outcome: MetricsOutcome::OUTCOME_UNSPECIFIED, + error_code: 1, + security_level: MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED, + // Default for bool is false (for key_upgraded field). + ..Default::default() + }; + + let mut key_operation_with_purpose_and_modes_info = KeyOperationWithPurposeAndModesInfo { + purpose: MetricsPurpose::KEY_PURPOSE_UNSPECIFIED, + // Default for i32 is 0 (for the remaining bitmap fields). + ..Default::default() + }; + + key_operation_with_general_info.security_level = process_security_level(sec_level); + + key_operation_with_general_info.key_upgraded = key_upgraded; + + key_operation_with_purpose_and_modes_info.purpose = match key_purpose { + KeyPurpose::ENCRYPT => MetricsPurpose::ENCRYPT, + KeyPurpose::DECRYPT => MetricsPurpose::DECRYPT, + KeyPurpose::SIGN => MetricsPurpose::SIGN, + KeyPurpose::VERIFY => MetricsPurpose::VERIFY, + KeyPurpose::WRAP_KEY => MetricsPurpose::WRAP_KEY, + KeyPurpose::AGREE_KEY => MetricsPurpose::AGREE_KEY, + KeyPurpose::ATTEST_KEY => MetricsPurpose::ATTEST_KEY, + _ => MetricsPurpose::KEY_PURPOSE_UNSPECIFIED, + }; + + key_operation_with_general_info.outcome = match op_outcome { + Outcome::Unknown | Outcome::Dropped => MetricsOutcome::DROPPED, + Outcome::Success => MetricsOutcome::SUCCESS, + Outcome::Abort => MetricsOutcome::ABORT, + Outcome::Pruned => MetricsOutcome::PRUNED, + Outcome::ErrorCode(e) => { + key_operation_with_general_info.error_code = e.0; + MetricsOutcome::ERROR + } + }; + + for key_param in op_params.iter().map(KsKeyParamValue::from) { + match key_param { + KsKeyParamValue::PaddingMode(p) => { + compute_padding_mode_bitmap( + &mut key_operation_with_purpose_and_modes_info.padding_mode_bitmap, + p, + ); + } + KsKeyParamValue::Digest(d) => { + compute_digest_bitmap( + &mut key_operation_with_purpose_and_modes_info.digest_bitmap, + d, + ); + } + KsKeyParamValue::BlockMode(b) => { + compute_block_mode_bitmap( + &mut key_operation_with_purpose_and_modes_info.block_mode_bitmap, + b, + ); + } + _ => {} + } + } + + ( + KeystoreAtomPayload::KeyOperationWithGeneralInfo(key_operation_with_general_info), + KeystoreAtomPayload::KeyOperationWithPurposeAndModesInfo( + key_operation_with_purpose_and_modes_info, + ), + ) +} + +fn process_security_level(sec_level: SecurityLevel) -> MetricsSecurityLevel { + match sec_level { + SecurityLevel::SOFTWARE => MetricsSecurityLevel::SECURITY_LEVEL_SOFTWARE, + SecurityLevel::TRUSTED_ENVIRONMENT => { + MetricsSecurityLevel::SECURITY_LEVEL_TRUSTED_ENVIRONMENT + } + SecurityLevel::STRONGBOX => MetricsSecurityLevel::SECURITY_LEVEL_STRONGBOX, + SecurityLevel::KEYSTORE => MetricsSecurityLevel::SECURITY_LEVEL_KEYSTORE, + _ => MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED, + } +} + +fn compute_padding_mode_bitmap(padding_mode_bitmap: &mut i32, padding_mode: PaddingMode) { + match padding_mode { + PaddingMode::NONE => { + *padding_mode_bitmap |= 1 << PaddingModeBitPosition::NONE_BIT_POSITION as i32; + } + PaddingMode::RSA_OAEP => { + *padding_mode_bitmap |= 1 << PaddingModeBitPosition::RSA_OAEP_BIT_POS as i32; + } + PaddingMode::RSA_PSS => { + *padding_mode_bitmap |= 1 << PaddingModeBitPosition::RSA_PSS_BIT_POS as i32; + } + PaddingMode::RSA_PKCS1_1_5_ENCRYPT => { + *padding_mode_bitmap |= + 1 << PaddingModeBitPosition::RSA_PKCS1_1_5_ENCRYPT_BIT_POS as i32; + } + PaddingMode::RSA_PKCS1_1_5_SIGN => { + *padding_mode_bitmap |= 1 << PaddingModeBitPosition::RSA_PKCS1_1_5_SIGN_BIT_POS as i32; + } + PaddingMode::PKCS7 => { + *padding_mode_bitmap |= 1 << PaddingModeBitPosition::PKCS7_BIT_POS as i32; + } + _ => {} + } +} + +fn compute_digest_bitmap(digest_bitmap: &mut i32, digest: Digest) { + match digest { + Digest::NONE => { + *digest_bitmap |= 1 << DigestBitPosition::NONE_BIT_POSITION as i32; + } + Digest::MD5 => { + *digest_bitmap |= 1 << DigestBitPosition::MD5_BIT_POS as i32; + } + Digest::SHA1 => { + *digest_bitmap |= 1 << DigestBitPosition::SHA_1_BIT_POS as i32; + } + Digest::SHA_2_224 => { + *digest_bitmap |= 1 << DigestBitPosition::SHA_2_224_BIT_POS as i32; + } + Digest::SHA_2_256 => { + *digest_bitmap |= 1 << DigestBitPosition::SHA_2_256_BIT_POS as i32; + } + Digest::SHA_2_384 => { + *digest_bitmap |= 1 << DigestBitPosition::SHA_2_384_BIT_POS as i32; + } + Digest::SHA_2_512 => { + *digest_bitmap |= 1 << DigestBitPosition::SHA_2_512_BIT_POS as i32; + } + _ => {} + } +} + +fn compute_block_mode_bitmap(block_mode_bitmap: &mut i32, block_mode: BlockMode) { + match block_mode { + BlockMode::ECB => { + *block_mode_bitmap |= 1 << BlockModeBitPosition::ECB_BIT_POS as i32; + } + BlockMode::CBC => { + *block_mode_bitmap |= 1 << BlockModeBitPosition::CBC_BIT_POS as i32; + } + BlockMode::CTR => { + *block_mode_bitmap |= 1 << BlockModeBitPosition::CTR_BIT_POS as i32; + } + BlockMode::GCM => { + *block_mode_bitmap |= 1 << BlockModeBitPosition::GCM_BIT_POS as i32; + } + _ => {} + } +} + +fn compute_purpose_bitmap(purpose_bitmap: &mut i32, purpose: KeyPurpose) { + match purpose { + KeyPurpose::ENCRYPT => { + *purpose_bitmap |= 1 << KeyPurposeBitPosition::ENCRYPT_BIT_POS as i32; + } + KeyPurpose::DECRYPT => { + *purpose_bitmap |= 1 << KeyPurposeBitPosition::DECRYPT_BIT_POS as i32; + } + KeyPurpose::SIGN => { + *purpose_bitmap |= 1 << KeyPurposeBitPosition::SIGN_BIT_POS as i32; + } + KeyPurpose::VERIFY => { + *purpose_bitmap |= 1 << KeyPurposeBitPosition::VERIFY_BIT_POS as i32; + } + KeyPurpose::WRAP_KEY => { + *purpose_bitmap |= 1 << KeyPurposeBitPosition::WRAP_KEY_BIT_POS as i32; + } + KeyPurpose::AGREE_KEY => { + *purpose_bitmap |= 1 << KeyPurposeBitPosition::AGREE_KEY_BIT_POS as i32; + } + KeyPurpose::ATTEST_KEY => { + *purpose_bitmap |= 1 << KeyPurposeBitPosition::ATTEST_KEY_BIT_POS as i32; + } + _ => {} + } +} + +pub(crate) fn pull_storage_stats() -> Result> { + let mut atom_vec: Vec = Vec::new(); + let mut append = |stat| { + match stat { + Ok(s) => atom_vec.push(KeystoreAtom { + payload: KeystoreAtomPayload::StorageStats(s), + ..Default::default() + }), + Err(error) => { + log::error!("pull_metrics_callback: Error getting storage stat: {}", error) + } + }; + }; + DB.with(|db| { + let mut db = db.borrow_mut(); + append(db.get_storage_stat(MetricsStorage::DATABASE)); + append(db.get_storage_stat(MetricsStorage::KEY_ENTRY)); + append(db.get_storage_stat(MetricsStorage::KEY_ENTRY_ID_INDEX)); + append(db.get_storage_stat(MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX)); + append(db.get_storage_stat(MetricsStorage::BLOB_ENTRY)); + append(db.get_storage_stat(MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX)); + append(db.get_storage_stat(MetricsStorage::KEY_PARAMETER)); + append(db.get_storage_stat(MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX)); + append(db.get_storage_stat(MetricsStorage::KEY_METADATA)); + append(db.get_storage_stat(MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX)); + append(db.get_storage_stat(MetricsStorage::GRANT)); + append(db.get_storage_stat(MetricsStorage::AUTH_TOKEN)); + append(db.get_storage_stat(MetricsStorage::BLOB_METADATA)); + append(db.get_storage_stat(MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX)); + }); + Ok(atom_vec) +} + +/// Log error events related to Remote Key Provisioning (RKP). +pub fn log_rkp_error_stats(rkp_error: MetricsRkpError, sec_level: &SecurityLevel) { + let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats(RkpErrorStats { + rkpError: rkp_error, + security_level: process_security_level(*sec_level), + }); + METRICS_STORE.insert_atom(AtomID::RKP_ERROR_STATS, rkp_error_stats); +} + +/// This function tries to read and update the system property: keystore.crash_count. +/// If the property is absent, it sets the property with value 0. If the property is present, it +/// increments the value. This helps tracking keystore crashes internally. +pub fn update_keystore_crash_sysprop() { + let new_count = match read_keystore_crash_count() { + Ok(Some(count)) => count + 1, + // If the property is absent, then this is the first start up during the boot. + // Proceed to write the system property with value 0. + Ok(None) => 0, + Err(error) => { + log::warn!( + concat!( + "In update_keystore_crash_sysprop: ", + "Failed to read the existing system property due to: {:?}.", + "Therefore, keystore crashes will not be logged." + ), + error + ); + return; + } + }; + if let Err(e) = + rsproperties::set(KEYSTORE_CRASH_COUNT_PROPERTY, &new_count.to_string()) + { + log::error!( + concat!( + "In update_keystore_crash_sysprop:: ", + "Failed to write the system property due to error: {:?}" + ), + e + ); + } +} + +/// Read the system property: keystore.crash_count. +pub fn read_keystore_crash_count() -> Result> { + let sp: std::result::Result = rsproperties::get(KEYSTORE_CRASH_COUNT_PROPERTY); + match sp { + Ok(count_str) => { + let count = count_str.parse::()?; + Ok(Some(count)) + } + Err(rsproperties::Error::NotFound(_)) => Ok(None), + Err(e) => Err(e).context(err!("Failed to read crash count property.")), + } +} + +/// Enum defining the bit position for each padding mode. Since padding mode can be repeatable, it +/// is represented using a bitmap. +#[allow(non_camel_case_types)] +#[repr(i32)] +enum PaddingModeBitPosition { + ///Bit position in the PaddingMode bitmap for NONE. + NONE_BIT_POSITION = 0, + ///Bit position in the PaddingMode bitmap for RSA_OAEP. + RSA_OAEP_BIT_POS = 1, + ///Bit position in the PaddingMode bitmap for RSA_PSS. + RSA_PSS_BIT_POS = 2, + ///Bit position in the PaddingMode bitmap for RSA_PKCS1_1_5_ENCRYPT. + RSA_PKCS1_1_5_ENCRYPT_BIT_POS = 3, + ///Bit position in the PaddingMode bitmap for RSA_PKCS1_1_5_SIGN. + RSA_PKCS1_1_5_SIGN_BIT_POS = 4, + ///Bit position in the PaddingMode bitmap for RSA_PKCS7. + PKCS7_BIT_POS = 5, +} + +/// Enum defining the bit position for each digest type. Since digest can be repeatable in +/// key parameters, it is represented using a bitmap. +#[allow(non_camel_case_types)] +#[repr(i32)] +enum DigestBitPosition { + ///Bit position in the Digest bitmap for NONE. + NONE_BIT_POSITION = 0, + ///Bit position in the Digest bitmap for MD5. + MD5_BIT_POS = 1, + ///Bit position in the Digest bitmap for SHA1. + SHA_1_BIT_POS = 2, + ///Bit position in the Digest bitmap for SHA_2_224. + SHA_2_224_BIT_POS = 3, + ///Bit position in the Digest bitmap for SHA_2_256. + SHA_2_256_BIT_POS = 4, + ///Bit position in the Digest bitmap for SHA_2_384. + SHA_2_384_BIT_POS = 5, + ///Bit position in the Digest bitmap for SHA_2_512. + SHA_2_512_BIT_POS = 6, +} + +/// Enum defining the bit position for each block mode type. Since block mode can be repeatable in +/// key parameters, it is represented using a bitmap. +#[allow(non_camel_case_types)] +#[repr(i32)] +enum BlockModeBitPosition { + ///Bit position in the BlockMode bitmap for ECB. + ECB_BIT_POS = 1, + ///Bit position in the BlockMode bitmap for CBC. + CBC_BIT_POS = 2, + ///Bit position in the BlockMode bitmap for CTR. + CTR_BIT_POS = 3, + ///Bit position in the BlockMode bitmap for GCM. + GCM_BIT_POS = 4, +} + +/// Enum defining the bit position for each key purpose. Since key purpose can be repeatable in +/// key parameters, it is represented using a bitmap. +#[allow(non_camel_case_types)] +#[repr(i32)] +enum KeyPurposeBitPosition { + ///Bit position in the KeyPurpose bitmap for Encrypt. + ENCRYPT_BIT_POS = 1, + ///Bit position in the KeyPurpose bitmap for Decrypt. + DECRYPT_BIT_POS = 2, + ///Bit position in the KeyPurpose bitmap for Sign. + SIGN_BIT_POS = 3, + ///Bit position in the KeyPurpose bitmap for Verify. + VERIFY_BIT_POS = 4, + ///Bit position in the KeyPurpose bitmap for Wrap Key. + WRAP_KEY_BIT_POS = 5, + ///Bit position in the KeyPurpose bitmap for Agree Key. + AGREE_KEY_BIT_POS = 6, + ///Bit position in the KeyPurpose bitmap for Attest Key. + ATTEST_KEY_BIT_POS = 7, +} + +/// The various metrics-related types are not defined in this crate, so the orphan +/// trait rule means that `std::fmt::Debug` cannot be implemented for them. +/// Instead, create our own local trait that generates a debug string for a type. +trait Summary { + fn show(&self) -> String; +} + +/// Implement the [`Summary`] trait for AIDL-derived pseudo-enums, mapping named enum values to +/// specified short names, all padded with spaces to the specified width (to allow improved +/// readability when printed in a group). +macro_rules! impl_summary_enum { + { $enum:ident, $width:literal, $( $variant:ident => $short:literal ),+ $(,)? } => { + impl Summary for $enum{ + fn show(&self) -> String { + match self.0 { + $( + x if x == Self::$variant.0 => format!(concat!("{:", + stringify!($width), + "}"), + $short), + )* + v => format!("Unknown({})", v), + } + } + } + } +} + +impl_summary_enum!(AtomID, 14, + STORAGE_STATS => "STORAGE", + KEYSTORE2_ATOM_WITH_OVERFLOW => "OVERFLOW", + KEY_CREATION_WITH_GENERAL_INFO => "KEYGEN_GENERAL", + KEY_CREATION_WITH_AUTH_INFO => "KEYGEN_AUTH", + KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO => "KEYGEN_MODES", + KEY_OPERATION_WITH_PURPOSE_AND_MODES_INFO => "KEYOP_MODES", + KEY_OPERATION_WITH_GENERAL_INFO => "KEYOP_GENERAL", + RKP_ERROR_STATS => "RKP_ERR", + CRASH_STATS => "CRASH", +); + +impl_summary_enum!(MetricsStorage, 28, + STORAGE_UNSPECIFIED => "UNSPECIFIED", + KEY_ENTRY => "KEY_ENTRY", + KEY_ENTRY_ID_INDEX => "KEY_ENTRY_ID_IDX" , + KEY_ENTRY_DOMAIN_NAMESPACE_INDEX => "KEY_ENTRY_DOMAIN_NS_IDX" , + BLOB_ENTRY => "BLOB_ENTRY", + BLOB_ENTRY_KEY_ENTRY_ID_INDEX => "BLOB_ENTRY_KEY_ENTRY_ID_IDX" , + KEY_PARAMETER => "KEY_PARAMETER", + KEY_PARAMETER_KEY_ENTRY_ID_INDEX => "KEY_PARAM_KEY_ENTRY_ID_IDX" , + KEY_METADATA => "KEY_METADATA", + KEY_METADATA_KEY_ENTRY_ID_INDEX => "KEY_META_KEY_ENTRY_ID_IDX" , + GRANT => "GRANT", + AUTH_TOKEN => "AUTH_TOKEN", + BLOB_METADATA => "BLOB_METADATA", + BLOB_METADATA_BLOB_ENTRY_ID_INDEX => "BLOB_META_BLOB_ENTRY_ID_IDX" , + METADATA => "METADATA", + DATABASE => "DATABASE", + LEGACY_STORAGE => "LEGACY_STORAGE", +); + +impl_summary_enum!(MetricsAlgorithm, 4, + ALGORITHM_UNSPECIFIED => "NONE", + RSA => "RSA", + EC => "EC", + AES => "AES", + TRIPLE_DES => "DES", + HMAC => "HMAC", +); + +impl_summary_enum!(MetricsEcCurve, 5, + EC_CURVE_UNSPECIFIED => "NONE", + P_224 => "P-224", + P_256 => "P-256", + P_384 => "P-384", + P_521 => "P-521", + CURVE_25519 => "25519", +); + +impl_summary_enum!(MetricsKeyOrigin, 10, + ORIGIN_UNSPECIFIED => "UNSPEC", + GENERATED => "GENERATED", + DERIVED => "DERIVED", + IMPORTED => "IMPORTED", + RESERVED => "RESERVED", + SECURELY_IMPORTED => "SEC-IMPORT", +); + +impl_summary_enum!(MetricsSecurityLevel, 9, + SECURITY_LEVEL_UNSPECIFIED => "UNSPEC", + SECURITY_LEVEL_SOFTWARE => "SOFTWARE", + SECURITY_LEVEL_TRUSTED_ENVIRONMENT => "TEE", + SECURITY_LEVEL_STRONGBOX => "STRONGBOX", + SECURITY_LEVEL_KEYSTORE => "KEYSTORE", +); + +// Metrics values for HardwareAuthenticatorType are broken -- the AIDL type is a bitmask +// not an enum, so offseting the enum values by 1 doesn't work. +impl_summary_enum!(MetricsHardwareAuthenticatorType, 6, + AUTH_TYPE_UNSPECIFIED => "UNSPEC", + NONE => "NONE", + PASSWORD => "PASSWD", + FINGERPRINT => "FPRINT", + ANY => "ANY", +); + +impl_summary_enum!(MetricsPurpose, 7, + KEY_PURPOSE_UNSPECIFIED => "UNSPEC", + ENCRYPT => "ENCRYPT", + DECRYPT => "DECRYPT", + SIGN => "SIGN", + VERIFY => "VERIFY", + WRAP_KEY => "WRAPKEY", + AGREE_KEY => "AGREEKY", + ATTEST_KEY => "ATTESTK", +); + +impl_summary_enum!(MetricsOutcome, 7, + OUTCOME_UNSPECIFIED => "UNSPEC", + DROPPED => "DROPPED", + SUCCESS => "SUCCESS", + ABORT => "ABORT", + PRUNED => "PRUNED", + ERROR => "ERROR", +); + +impl_summary_enum!(MetricsRkpError, 6, + RKP_ERROR_UNSPECIFIED => "UNSPEC", + OUT_OF_KEYS => "OOKEYS", + FALL_BACK_DURING_HYBRID => "FALLBK", +); + +/// Convert an argument into a corresponding format clause. (This is needed because +/// macro expansion text for repeated inputs needs to mention one of the repeated +/// inputs.) +macro_rules! format_clause { + { $ignored:ident } => { "{}" } +} + +/// Generate code to print a string corresponding to a bitmask, where the given +/// enum identifies which bits mean what. If additional bits (not included in +/// the enum variants) are set, include the whole bitmask in the output so no +/// information is lost. +macro_rules! show_enum_bitmask { + { $v:expr, $enum:ident, $( $variant:ident => $short:literal ),+ $(,)? } => { + { + let v: i32 = $v; + let mut displayed_mask = 0i32; + $( + displayed_mask |= 1 << $enum::$variant as i32; + )* + let undisplayed_mask = !displayed_mask; + let undisplayed = v & undisplayed_mask; + let extra = if undisplayed == 0 { + "".to_string() + } else { + format!("(full:{v:#010x})") + }; + format!( + concat!( $( format_clause!($variant), )* "{}"), + $( + if v & 1 << $enum::$variant as i32 != 0 { $short } else { "-" }, + )* + extra + ) + } + } +} + +fn show_purpose(v: i32) -> String { + show_enum_bitmask!(v, KeyPurposeBitPosition, + ATTEST_KEY_BIT_POS => "A", + AGREE_KEY_BIT_POS => "G", + WRAP_KEY_BIT_POS => "W", + VERIFY_BIT_POS => "V", + SIGN_BIT_POS => "S", + DECRYPT_BIT_POS => "D", + ENCRYPT_BIT_POS => "E", + ) +} + +fn show_padding(v: i32) -> String { + show_enum_bitmask!(v, PaddingModeBitPosition, + PKCS7_BIT_POS => "7", + RSA_PKCS1_1_5_SIGN_BIT_POS => "S", + RSA_PKCS1_1_5_ENCRYPT_BIT_POS => "E", + RSA_PSS_BIT_POS => "P", + RSA_OAEP_BIT_POS => "O", + NONE_BIT_POSITION => "N", + ) +} + +fn show_digest(v: i32) -> String { + show_enum_bitmask!(v, DigestBitPosition, + SHA_2_512_BIT_POS => "5", + SHA_2_384_BIT_POS => "3", + SHA_2_256_BIT_POS => "2", + SHA_2_224_BIT_POS => "4", + SHA_1_BIT_POS => "1", + MD5_BIT_POS => "M", + NONE_BIT_POSITION => "N", + ) +} + +fn show_blockmode(v: i32) -> String { + show_enum_bitmask!(v, BlockModeBitPosition, + GCM_BIT_POS => "G", + CTR_BIT_POS => "T", + CBC_BIT_POS => "C", + ECB_BIT_POS => "E", + ) +} + +impl Summary for KeystoreAtomPayload { + fn show(&self) -> String { + match self { + KeystoreAtomPayload::StorageStats(v) => { + format!("{} sz={} unused={}", v.storage_type.show(), v.size, v.unused_size) + } + KeystoreAtomPayload::KeyCreationWithGeneralInfo(v) => { + format!( + "{} ksz={:>4} crv={} {} rc={:4} attest? {}", + v.algorithm.show(), + v.key_size, + v.ec_curve.show(), + v.key_origin.show(), + v.error_code, + if v.attestation_requested { "Y" } else { "N" } + ) + } + KeystoreAtomPayload::KeyCreationWithAuthInfo(v) => { + format!( + "auth={} log(time)={:3} sec={}", + v.user_auth_type.show(), + v.log10_auth_key_timeout_seconds, + v.security_level.show() + ) + } + KeystoreAtomPayload::KeyCreationWithPurposeAndModesInfo(v) => { + format!( + "{} purpose={} padding={} digest={} blockmode={}", + v.algorithm.show(), + show_purpose(v.purpose_bitmap), + show_padding(v.padding_mode_bitmap), + show_digest(v.digest_bitmap), + show_blockmode(v.block_mode_bitmap), + ) + } + KeystoreAtomPayload::KeyOperationWithGeneralInfo(v) => { + format!( + "{} {:>8} upgraded? {} sec={}", + v.outcome.show(), + v.error_code, + if v.key_upgraded { "Y" } else { "N" }, + v.security_level.show() + ) + } + KeystoreAtomPayload::KeyOperationWithPurposeAndModesInfo(v) => { + format!( + "{} padding={} digest={} blockmode={}", + v.purpose.show(), + show_padding(v.padding_mode_bitmap), + show_digest(v.digest_bitmap), + show_blockmode(v.block_mode_bitmap) + ) + } + KeystoreAtomPayload::RkpErrorStats(v) => { + format!("{} sec={}", v.rkpError.show(), v.security_level.show()) + } + KeystoreAtomPayload::CrashStats(v) => { + format!("count={}", v.count_of_crash_events) + } + KeystoreAtomPayload::Keystore2AtomWithOverflow(v) => { + format!("atom={}", v.atom_id.show()) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_enum_show() { + let algo = MetricsAlgorithm::RSA; + assert_eq!("RSA ", algo.show()); + let algo = MetricsAlgorithm(42); + assert_eq!("Unknown(42)", algo.show()); + } + + #[test] + fn test_enum_bitmask_show() { + let mut modes = 0i32; + compute_block_mode_bitmap(&mut modes, BlockMode::ECB); + compute_block_mode_bitmap(&mut modes, BlockMode::CTR); + + assert_eq!(show_blockmode(modes), "-T-E"); + + // Add some bits not covered by the enum of valid bit positions. + modes |= 0xa0; + assert_eq!(show_blockmode(modes), "-T-E(full:0x000000aa)"); + modes |= 0x300; + assert_eq!(show_blockmode(modes), "-T-E(full:0x000003aa)"); + } +} diff --git a/libs/rust/src/keymaster/mod.rs b/libs/rust/src/keymaster/mod.rs index 26b0f98..e1d2581 100644 --- a/libs/rust/src/keymaster/mod.rs +++ b/libs/rust/src/keymaster/mod.rs @@ -2,12 +2,14 @@ pub mod attestation_key_utils; pub mod boot_key; pub mod crypto; pub mod database; +pub mod metrics_store; pub mod db; pub mod enforcements; pub mod error; pub mod key_parameter; pub mod keymint_device; pub mod keymint_operation; +pub mod operation; pub mod permission; pub mod security_level; pub mod super_key; diff --git a/libs/rust/src/keymaster/operation.rs b/libs/rust/src/keymaster/operation.rs new file mode 100644 index 0000000..a46dc76 --- /dev/null +++ b/libs/rust/src/keymaster/operation.rs @@ -0,0 +1,871 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This crate implements the `IKeystoreOperation` AIDL interface, which represents +//! an ongoing key operation, as well as the operation database, which is mainly +//! required for tracking operations for the purpose of pruning. +//! This crate also implements an operation pruning strategy. +//! +//! Operations implement the API calls update, finish, and abort. +//! Additionally, an operation can be dropped and pruned. The former +//! happens if the client deletes a binder to the operation object. +//! An existing operation may get pruned when running out of operation +//! slots and a new operation takes precedence. +//! +//! ## Operation Lifecycle +//! An operation gets created when the client calls `IKeystoreSecurityLevel::create`. +//! It may receive zero or more update request. The lifecycle ends when: +//! * `update` yields an error. +//! * `finish` is called. +//! * `abort` is called. +//! * The operation gets dropped. +//! * The operation gets pruned. +//! +//! `Operation` has an `Outcome` member. While the outcome is `Outcome::Unknown`, +//! the operation is active and in a good state. Any of the above conditions may +//! change the outcome to one of the defined outcomes Success, Abort, Dropped, +//! Pruned, or ErrorCode. The latter is chosen in the case of an unexpected error, during +//! `update` or `finish`. `Success` is chosen iff `finish` completes without error. +//! Note that all operations get dropped eventually in the sense that they lose +//! their last reference and get destroyed. At that point, the fate of the operation +//! gets logged. However, an operation will transition to `Outcome::Dropped` iff +//! the operation was still active (`Outcome::Unknown`) at that time. +//! +//! ## Operation Dropping +//! To observe the dropping of an operation, we have to make sure that there +//! are no strong references to the IBinder representing this operation. +//! This would be simple enough if the operation object would need to be accessed +//! only by transactions. But to perform pruning, we have to retain a reference to the +//! original operation object. +//! +//! ## Operation Pruning +//! Pruning an operation happens during the creation of a new operation. +//! We have to iterate through the operation database to find a suitable +//! candidate. Then we abort and finalize this operation setting its outcome to +//! `Outcome::Pruned`. The corresponding KeyMint operation slot will have been freed +//! up at this point, but the `Operation` object lingers. When the client +//! attempts to use the operation again they will receive +//! ErrorCode::INVALID_OPERATION_HANDLE indicating that the operation no longer +//! exits. This should be the cue for the client to destroy its binder. +//! At that point the operation gets dropped. +//! +//! ## Architecture +//! The `IKeystoreOperation` trait is implemented by `KeystoreOperation`. +//! This acts as a proxy object holding a strong reference to actual operation +//! implementation `Operation`. +//! +//! ``` +//! struct KeystoreOperation { +//! operation: Mutex>>, +//! } +//! ``` +//! +//! The `Mutex` serves two purposes. It provides interior mutability allowing +//! us to set the Option to None. We do this when the life cycle ends during +//! a call to `update`, `finish`, or `abort`. As a result most of the Operation +//! related resources are freed. The `KeystoreOperation` proxy object still +//! lingers until dropped by the client. +//! The second purpose is to protect operations against concurrent usage. +//! Failing to lock this mutex yields `ResponseCode::OPERATION_BUSY` and indicates +//! a programming error in the client. +//! +//! Note that the Mutex only protects the operation against concurrent client calls. +//! We still retain weak references to the operation in the operation database: +//! +//! ``` +//! struct OperationDb { +//! operations: Mutex>> +//! } +//! ``` +//! +//! This allows us to access the operations for the purpose of pruning. +//! We do this in three phases. +//! 1. We gather the pruning information. Besides non mutable information, +//! we access `last_usage` which is protected by a mutex. +//! We only lock this mutex for single statements at a time. During +//! this phase we hold the operation db lock. +//! 2. We choose a pruning candidate by computing the pruning resistance +//! of each operation. We do this entirely with information we now +//! have on the stack without holding any locks. +//! (See `OperationDb::prune` for more details on the pruning strategy.) +//! 3. During pruning we briefly lock the operation database again to get the +//! the pruning candidate by index. We then attempt to abort the candidate. +//! If the candidate was touched in the meantime or is currently fulfilling +//! a request (i.e., the client calls update, finish, or abort), +//! we go back to 1 and try again. +//! +//! So the outer Mutex in `KeystoreOperation::operation` only protects +//! operations against concurrent client calls but not against concurrent +//! pruning attempts. This is what the `Operation::outcome` mutex is used for. +//! +//! ``` +//! struct Operation { +//! ... +//! outcome: Mutex, +//! ... +//! } +//! ``` +//! +//! Any request that can change the outcome, i.e., `update`, `finish`, `abort`, +//! `drop`, and `prune` has to take the outcome lock and check if the outcome +//! is still `Outcome::Unknown` before entering. `prune` is special in that +//! it will `try_lock`, because we don't want to be blocked on a potentially +//! long running request at another operation. If it fails to get the lock +//! the operation is either being touched, which changes its pruning resistance, +//! or it transitions to its end-of-life, which means we may get a free slot. +//! Either way, we have to revaluate the pruning scores. + +use crate::android::hardware::security::keymint::ErrorCode::ErrorCode; +use crate::android::system::keystore2::ResponseCode::ResponseCode; +use crate::keymaster::enforcements::AuthInfo; + +use crate::keymaster::error::{KsError as Error, SerializedError, error_to_serialized_error, into_binder, into_logged_binder, map_binder_status}; + +use crate::err; +use crate::keymaster::metrics_store::log_key_operation_event_stats; +use crate::watchdog as wd; +use crate::android::hardware::security::keymint::{ + IKeyMintOperation::IKeyMintOperation, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, + SecurityLevel::SecurityLevel, +}; +use crate::android::system::keystore2::{ + IKeystoreOperation::BnKeystoreOperation, IKeystoreOperation::IKeystoreOperation, +}; +use anyhow::{anyhow, Context, Result}; +use rsbinder::{Status, Strong}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex, MutexGuard, Weak}, + time::Duration, + time::Instant, +}; + +/// Operations have `Outcome::Unknown` as long as they are active. They transition +/// to one of the other variants exactly once. The distinction in outcome is mainly +/// for the statistic. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Outcome { + /// Operations have `Outcome::Unknown` as long as they are active. + Unknown, + /// Operation is successful. + Success, + /// Operation is aborted. + Abort, + /// Operation is dropped. + Dropped, + /// Operation is pruned. + Pruned, + /// Operation is failed with the error code. + ErrorCode(SerializedError), +} + +/// Operation bundles all of the operation related resources and tracks the operation's +/// outcome. +#[derive(Debug)] +pub struct Operation { + // The index of this operation in the OperationDb. + index: usize, + km_op: Strong, + last_usage: Mutex, + outcome: Mutex, + owner: u32, // Uid of the operation's owner. + auth_info: Mutex, + forced: bool, + logging_info: LoggingInfo, +} + +/// Keeps track of the information required for logging operations. +#[derive(Debug)] +pub struct LoggingInfo { + sec_level: SecurityLevel, + purpose: KeyPurpose, + op_params: Vec, + key_upgraded: bool, +} + +impl LoggingInfo { + /// Constructor + pub fn new( + sec_level: SecurityLevel, + purpose: KeyPurpose, + op_params: Vec, + key_upgraded: bool, + ) -> LoggingInfo { + Self { sec_level, purpose, op_params, key_upgraded } + } +} + +struct PruningInfo { + last_usage: Instant, + owner: u32, + index: usize, + forced: bool, +} + +// We don't except more than 32KiB of data in `update`, `updateAad`, and `finish`. +const MAX_RECEIVE_DATA: usize = 0x8000; + +impl Operation { + /// Constructor + pub fn new( + index: usize, + km_op: rsbinder::Strong, + owner: u32, + auth_info: AuthInfo, + forced: bool, + logging_info: LoggingInfo, + ) -> Self { + Self { + index, + km_op, + last_usage: Mutex::new(Instant::now()), + outcome: Mutex::new(Outcome::Unknown), + owner, + auth_info: Mutex::new(auth_info), + forced, + logging_info, + } + } + + fn get_pruning_info(&self) -> Option { + // An operation may be finalized. + if let Ok(guard) = self.outcome.try_lock() { + match *guard { + Outcome::Unknown => {} + // If the outcome is any other than unknown, it has been finalized, + // and we can no longer consider it for pruning. + _ => return None, + } + } + // Else: If we could not grab the lock, this means that the operation is currently + // being used and it may be transitioning to finalized or it was simply updated. + // In any case it is fair game to consider it for pruning. If the operation + // transitioned to a final state, we will notice when we attempt to prune, and + // a subsequent attempt to create a new operation will succeed. + Some(PruningInfo { + // Expect safety: + // `last_usage` is locked only for primitive single line statements. + // There is no chance to panic and poison the mutex. + last_usage: *self.last_usage.lock().expect("In get_pruning_info."), + owner: self.owner, + index: self.index, + forced: self.forced, + }) + } + + fn prune(&self, last_usage: Instant) -> Result<(), Error> { + let mut locked_outcome = match self.outcome.try_lock() { + Ok(guard) => match *guard { + Outcome::Unknown => guard, + _ => return Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)), + }, + Err(_) => return Err(Error::Rc(ResponseCode::OPERATION_BUSY)), + }; + + // In `OperationDb::prune`, which is our caller, we first gather the pruning + // information including the last usage. When we select a candidate + // we call `prune` on that candidate passing the last_usage + // that we gathered earlier. If the actual last usage + // has changed since than, it means the operation was busy in the + // meantime, which means that we have to reevaluate the pruning score. + // + // Expect safety: + // `last_usage` is locked only for primitive single line statements. + // There is no chance to panic and poison the mutex. + if *self.last_usage.lock().expect("In Operation::prune()") != last_usage { + return Err(Error::Rc(ResponseCode::OPERATION_BUSY)); + } + *locked_outcome = Outcome::Pruned; + + let _wp = wd::watch("Operation::prune: calling IKeyMintOperation::abort()"); + + // We abort the operation. If there was an error we log it but ignore it. + if let Err(e) = map_binder_status(self.km_op.abort()) { + log::warn!("In prune: KeyMint::abort failed with {:?}.", e); + } + + Ok(()) + } + + // This function takes a Result from a KeyMint call and inspects it for errors. + // If an error was found it updates the given `locked_outcome` accordingly. + // It forwards the Result unmodified. + // The precondition to this call must be *locked_outcome == Outcome::Unknown. + // Ideally the `locked_outcome` came from a successful call to `check_active` + // see below. + fn update_outcome( + &self, + locked_outcome: &mut Outcome, + err: Result, + ) -> Result { + if let Err(e) = &err { + *locked_outcome = Outcome::ErrorCode(error_to_serialized_error(e)) + } + err + } + + // This function grabs the outcome lock and checks the current outcome state. + // If the outcome is still `Outcome::Unknown`, this function returns + // the locked outcome for further updates. In any other case it returns + // ErrorCode::INVALID_OPERATION_HANDLE indicating that this operation has + // been finalized and is no longer active. + fn check_active(&self) -> Result> { + let guard = self.outcome.lock().expect("In check_active."); + match *guard { + Outcome::Unknown => Ok(guard), + _ => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) + .context(err!("Call on finalized operation with outcome: {:?}.", *guard)), + } + } + + // This function checks the amount of input data sent to us. We reject any buffer + // exceeding MAX_RECEIVE_DATA bytes as input to `update`, `update_aad`, and `finish` + // in order to force clients into using reasonable limits. + fn check_input_length(data: &[u8]) -> Result<()> { + if data.len() > MAX_RECEIVE_DATA { + // This error code is unique, no context required here. + return Err(anyhow!(Error::Rc(ResponseCode::TOO_MUCH_DATA))); + } + Ok(()) + } + + // Update the last usage to now. + fn touch(&self) { + // Expect safety: + // `last_usage` is locked only for primitive single line statements. + // There is no chance to panic and poison the mutex. + *self.last_usage.lock().expect("In touch.") = Instant::now(); + } + + /// Implementation of `IKeystoreOperation::updateAad`. + /// Refer to the AIDL spec at system/hardware/interfaces/keystore2 for details. + fn update_aad(&self, aad_input: &[u8]) -> Result<()> { + let mut outcome = self.check_active().context("In update_aad")?; + Self::check_input_length(aad_input).context("In update_aad")?; + self.touch(); + + let (hat, tst) = self + .auth_info + .lock() + .unwrap() + .before_update() + .context(err!("Trying to get auth tokens."))?; + + self.update_outcome(&mut outcome, { + let _wp = wd::watch("Operation::update_aad: calling IKeyMintOperation::updateAad"); + map_binder_status(self.km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref())) + }) + .context(err!("Update failed."))?; + + Ok(()) + } + + /// Implementation of `IKeystoreOperation::update`. + /// Refer to the AIDL spec at system/hardware/interfaces/keystore2 for details. + fn update(&self, input: &[u8]) -> Result>> { + let mut outcome = self.check_active().context("In update")?; + Self::check_input_length(input).context("In update")?; + self.touch(); + + let (hat, tst) = self + .auth_info + .lock() + .unwrap() + .before_update() + .context(err!("Trying to get auth tokens."))?; + + let output = self + .update_outcome(&mut outcome, { + let _wp = wd::watch("Operation::update: calling IKeyMintOperation::update"); + map_binder_status(self.km_op.update(input, hat.as_ref(), tst.as_ref())) + }) + .context(err!("Update failed."))?; + + if output.is_empty() { + Ok(None) + } else { + Ok(Some(output)) + } + } + + /// Implementation of `IKeystoreOperation::finish`. + /// Refer to the AIDL spec at system/hardware/interfaces/keystore2 for details. + fn finish(&self, input: Option<&[u8]>, signature: Option<&[u8]>) -> Result>> { + let mut outcome = self.check_active().context("In finish")?; + if let Some(input) = input { + Self::check_input_length(input).context("In finish")?; + } + self.touch(); + + let (hat, tst, confirmation_token) = self + .auth_info + .lock() + .unwrap() + .before_finish() + .context(err!("Trying to get auth tokens."))?; + + let output = self + .update_outcome(&mut outcome, { + let _wp = wd::watch("Operation::finish: calling IKeyMintOperation::finish"); + map_binder_status(self.km_op.finish( + input, + signature, + hat.as_ref(), + tst.as_ref(), + confirmation_token.as_deref(), + )) + }) + .context(err!("Finish failed."))?; + + self.auth_info.lock().unwrap().after_finish().context("In finish.")?; + + // At this point the operation concluded successfully. + *outcome = Outcome::Success; + + if output.is_empty() { + Ok(None) + } else { + Ok(Some(output)) + } + } + + /// Aborts the operation if it is active. IFF the operation is aborted the outcome is + /// set to `outcome`. `outcome` must reflect the reason for the abort. Since the operation + /// gets aborted `outcome` must not be `Operation::Success` or `Operation::Unknown`. + fn abort(&self, outcome: Outcome) -> Result<()> { + let mut locked_outcome = self.check_active().context("In abort")?; + *locked_outcome = outcome; + + { + let _wp = wd::watch("Operation::abort: calling IKeyMintOperation::abort"); + map_binder_status(self.km_op.abort()).context(err!("KeyMint::abort failed.")) + } + } +} + +impl Drop for Operation { + fn drop(&mut self) { + let guard = self.outcome.lock().expect("In drop."); + log_key_operation_event_stats( + self.logging_info.sec_level, + self.logging_info.purpose, + &(self.logging_info.op_params), + &guard, + self.logging_info.key_upgraded, + ); + if let Outcome::Unknown = *guard { + drop(guard); + // If the operation was still active we call abort, setting + // the outcome to `Outcome::Dropped` + if let Err(e) = self.abort(Outcome::Dropped) { + log::error!("While dropping Operation: abort failed:\n {:?}", e); + } + } + } +} + +/// The OperationDb holds weak references to all ongoing operations. +/// Its main purpose is to facilitate operation pruning. +#[derive(Debug, Default)] +pub struct OperationDb { + // TODO replace Vec with WeakTable when the weak_table crate becomes + // available. + operations: Mutex>>, +} + +impl OperationDb { + /// Creates a new OperationDb. + pub fn new() -> Self { + Self { operations: Mutex::new(Vec::new()) } + } + + /// Creates a new operation. + /// This function takes a KeyMint operation and an associated + /// owner uid and returns a new Operation wrapped in a `std::sync::Arc`. + pub fn create_operation( + &self, + km_op: rsbinder::Strong, + owner: u32, + auth_info: AuthInfo, + forced: bool, + logging_info: LoggingInfo, + ) -> Arc { + // We use unwrap because we don't allow code that can panic while locked. + let mut operations = self.operations.lock().expect("In create_operation."); + + let mut index: usize = 0; + // First we iterate through the operation slots to try and find an unused + // slot. If we don't find one, we append the new entry instead. + match (*operations).iter_mut().find(|s| { + index += 1; + s.upgrade().is_none() + }) { + Some(free_slot) => { + let new_op = Arc::new(Operation::new( + index - 1, + km_op, + owner, + auth_info, + forced, + logging_info, + )); + *free_slot = Arc::downgrade(&new_op); + new_op + } + None => { + let new_op = Arc::new(Operation::new( + operations.len(), + km_op, + owner, + auth_info, + forced, + logging_info, + )); + operations.push(Arc::downgrade(&new_op)); + new_op + } + } + } + + fn get(&self, index: usize) -> Option> { + self.operations.lock().expect("In OperationDb::get.").get(index).and_then(|op| op.upgrade()) + } + + /// Attempts to prune an operation. + /// + /// This function is used during operation creation, i.e., by + /// `KeystoreSecurityLevel::create_operation`, to try and free up an operation slot + /// if it got `ErrorCode::TOO_MANY_OPERATIONS` from the KeyMint backend. It is not + /// guaranteed that an operation slot is available after this call successfully + /// returned for various reasons. E.g., another thread may have snatched up the newly + /// available slot. Callers may have to call prune multiple times before they get a + /// free operation slot. Prune may also return `Err(Error::Rc(ResponseCode::BACKEND_BUSY))` + /// which indicates that no prunable operation was found. + /// + /// To find a suitable candidate we compute the malus for the caller and each existing + /// operation. The malus is the inverse of the pruning power (caller) or pruning + /// resistance (existing operation). + /// + /// The malus is based on the number of sibling operations and age. Sibling + /// operations are operations that have the same owner (UID). + /// + /// Every operation, existing or new, starts with a malus of 1. Every sibling + /// increases the malus by one. The age is the time since an operation was last touched. + /// It increases the malus by log6( + 1) rounded down to the next + /// integer. So the malus increases stepwise after 5s, 35s, 215s, ... + /// Of two operations with the same malus the least recently used one is considered + /// weaker. + /// + /// For the caller to be able to prune an operation it must find an operation + /// with a malus higher than its own. + /// + /// The malus can be expressed as + /// ``` + /// malus = 1 + no_of_siblings + floor(log6(age_in_seconds + 1)) + /// ``` + /// where the constant `1` accounts for the operation under consideration. + /// In reality we compute it as + /// ``` + /// caller_malus = 1 + running_siblings + /// ``` + /// because the new operation has no age and is not included in the `running_siblings`, + /// and + /// ``` + /// running_malus = running_siblings + floor(log6(age_in_seconds + 1)) + /// ``` + /// because a running operation is included in the `running_siblings` and it has + /// an age. + /// + /// ## Example + /// A caller with no running operations has a malus of 1. Young (age < 5s) operations + /// also with no siblings have a malus of one and cannot be pruned by the caller. + /// We have to find an operation that has at least one sibling or is older than 5s. + /// + /// A caller with one running operation has a malus of 2. Now even young siblings + /// or single child aging (5s <= age < 35s) operations are off limit. An aging + /// sibling of two, however, would have a malus of 3 and would be fair game. + /// + /// ## Rationale + /// Due to the limitation of KeyMint operation slots, we cannot get around pruning or + /// a single app could easily DoS KeyMint. + /// Keystore 1.0 used to always prune the least recently used operation. This at least + /// guaranteed that new operations can always be started. With the increased usage + /// of Keystore we saw increased pruning activity which can lead to a livelock + /// situation in the worst case. + /// + /// With the new pruning strategy we want to provide well behaved clients with + /// progress assurances while punishing DoS attempts. As a result of this + /// strategy we can be in the situation where no operation can be pruned and the + /// creation of a new operation fails. This allows single child operations which + /// are frequently updated to complete, thereby breaking up livelock situations + /// and facilitating system wide progress. + /// + /// ## Update + /// We also allow callers to cannibalize their own sibling operations if no other + /// slot can be found. In this case the least recently used sibling is pruned. + pub fn prune(&self, caller: u32, forced: bool) -> Result<(), Error> { + loop { + // Maps the uid of the owner to the number of operations that owner has + // (running_siblings). More operations per owner lowers the pruning + // resistance of the operations of that owner. Whereas the number of + // ongoing operations of the caller lowers the pruning power of the caller. + let mut owners: HashMap = HashMap::new(); + let mut pruning_info: Vec = Vec::new(); + + let now = Instant::now(); + self.operations + .lock() + .expect("In OperationDb::prune: Trying to lock self.operations.") + .iter() + .for_each(|op| { + if let Some(op) = op.upgrade() { + if let Some(p_info) = op.get_pruning_info() { + let owner = p_info.owner; + pruning_info.push(p_info); + // Count operations per owner. + *owners.entry(owner).or_insert(0) += 1; + } + } + }); + + // If the operation is forced, the caller has a malus of 0. + let caller_malus = if forced { 0 } else { 1u64 + *owners.entry(caller).or_default() }; + + // We iterate through all operations computing the malus and finding + // the candidate with the highest malus which must also be higher + // than the caller_malus. + struct CandidateInfo { + index: usize, + malus: u64, + last_usage: Instant, + age: Duration, + } + let mut oldest_caller_op: Option = None; + let candidate = pruning_info.iter().fold( + None, + |acc: Option, &PruningInfo { last_usage, owner, index, forced }| { + // Compute the age of the current operation. + let age = now + .checked_duration_since(last_usage) + .unwrap_or_else(|| Duration::new(0, 0)); + + // Find the least recently used sibling as an alternative pruning candidate. + if owner == caller { + if let Some(CandidateInfo { age: a, .. }) = oldest_caller_op { + if age > a { + oldest_caller_op = + Some(CandidateInfo { index, malus: 0, last_usage, age }); + } + } else { + oldest_caller_op = + Some(CandidateInfo { index, malus: 0, last_usage, age }); + } + } + + // Compute the malus of the current operation. + let malus = if forced { + // Forced operations have a malus of 0. And cannot even be pruned + // by other forced operations. + 0 + } else { + // Expect safety: Every owner in pruning_info was counted in + // the owners map. So this unwrap cannot panic. + *owners.get(&owner).expect( + "This is odd. We should have counted every owner in pruning_info.", + ) + ((age.as_secs() + 1) as f64).log(6.0).floor() as u64 + }; + + // Now check if the current operation is a viable/better candidate + // the one currently stored in the accumulator. + match acc { + // First we have to find any operation that is prunable by the caller. + None => { + if caller_malus < malus { + Some(CandidateInfo { index, malus, last_usage, age }) + } else { + None + } + } + // If we have found one we look for the operation with the worst score. + // If there is a tie, the older operation is considered weaker. + Some(CandidateInfo { index: i, malus: m, last_usage: l, age: a }) => { + if malus > m || (malus == m && age > a) { + Some(CandidateInfo { index, malus, last_usage, age }) + } else { + Some(CandidateInfo { index: i, malus: m, last_usage: l, age: a }) + } + } + } + }, + ); + + // If we did not find a suitable candidate we may cannibalize our oldest sibling. + let candidate = candidate.or(oldest_caller_op); + + match candidate { + Some(CandidateInfo { index, malus: _, last_usage, age: _ }) => { + match self.get(index) { + Some(op) => { + match op.prune(last_usage) { + // We successfully freed up a slot. + Ok(()) => break Ok(()), + // This means the operation we tried to prune was on its way + // out. It also means that the slot it had occupied was freed up. + Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => break Ok(()), + // This means the operation we tried to prune was currently + // servicing a request. There are two options. + // * Assume that it was touched, which means that its + // pruning resistance increased. In that case we have + // to start over and find another candidate. + // * Assume that the operation is transitioning to end-of-life. + // which means that we got a free slot for free. + // If we assume the first but the second is true, we prune + // a good operation without need (aggressive approach). + // If we assume the second but the first is true, our + // caller will attempt to create a new KeyMint operation, + // fail with `ErrorCode::TOO_MANY_OPERATIONS`, and call + // us again (conservative approach). + Err(Error::Rc(ResponseCode::OPERATION_BUSY)) => { + // We choose the conservative approach, because + // every needlessly pruned operation can impact + // the user experience. + // To switch to the aggressive approach replace + // the following line with `continue`. + break Ok(()); + } + + // The candidate may have been touched so the score + // has changed since our evaluation. + _ => continue, + } + } + // This index does not exist any more. The operation + // in this slot was dropped. Good news, a slot + // has freed up. + None => break Ok(()), + } + } + // We did not get a pruning candidate. + None => break Err(Error::Rc(ResponseCode::BACKEND_BUSY)), + } + } + } +} + +/// Implementation of IKeystoreOperation. +pub struct KeystoreOperation { + operation: Mutex>>, +} + +impl KeystoreOperation { + /// Creates a new operation instance wrapped in a + /// BnKeystoreOperation proxy object. It also enables + /// `BinderFeatures::set_requesting_sid` on the new interface, because + /// we need it for checking Keystore permissions. + pub fn new_native_binder(operation: Arc) -> rsbinder::Strong { + BnKeystoreOperation::new_binder( + Self { operation: Mutex::new(Some(operation)) }, + ) + } + + /// Grabs the outer operation mutex and calls `f` on the locked operation. + /// The function also deletes the operation if it returns with an error or if + /// `delete_op` is true. + fn with_locked_operation(&self, f: F, delete_op: bool) -> Result + where + for<'a> F: FnOnce(&'a Operation) -> Result, + { + let mut delete_op: bool = delete_op; + match self.operation.try_lock() { + Ok(mut mutex_guard) => { + let result = match &*mutex_guard { + Some(op) => { + let result = f(op); + // Any error here means we can discard the operation. + if result.is_err() { + delete_op = true; + } + result + } + None => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) + .context(err!("KeystoreOperation::with_locked_operation")), + }; + + if delete_op { + // We give up our reference to the Operation, thereby freeing up our + // internal resources and ending the wrapped KeyMint operation. + // This KeystoreOperation object will still be owned by an SpIBinder + // until the client drops its remote reference. + *mutex_guard = None; + } + result + } + Err(_) => Err(Error::Rc(ResponseCode::OPERATION_BUSY)) + .context(err!("KeystoreOperation::with_locked_operation")), + } + } +} + +impl rsbinder::Interface for KeystoreOperation {} + +#[allow(non_snake_case)] +impl IKeystoreOperation for KeystoreOperation { + fn updateAad(&self, aad_input: &[u8]) -> Result<(), Status> { + let _wp = wd::watch("IKeystoreOperation::updateAad"); + self.with_locked_operation( + |op| op.update_aad(aad_input).context(err!("KeystoreOperation::updateAad")), + false, + ) + .map_err(into_logged_binder) + } + + fn update(&self, input: &[u8]) -> Result>, Status> { + let _wp = wd::watch("IKeystoreOperation::update"); + self.with_locked_operation( + |op| op.update(input).context(err!("KeystoreOperation::update")), + false, + ) + .map_err(into_logged_binder) + } + fn finish( + &self, + input: Option<&[u8]>, + signature: Option<&[u8]>, + ) -> Result>, Status> { + let _wp = wd::watch("IKeystoreOperation::finish"); + self.with_locked_operation( + |op| op.finish(input, signature).context(err!("KeystoreOperation::finish")), + true, + ) + .map_err(into_logged_binder) + } + + fn abort(&self) -> Result<(), Status> { + let _wp = wd::watch("IKeystoreOperation::abort"); + let result = self.with_locked_operation( + |op| op.abort(Outcome::Abort).context(err!("KeystoreOperation::abort")), + true, + ); + result.map_err(|e| { + match e.root_cause().downcast_ref::() { + // Calling abort on expired operations is something very common. + // There is no reason to clutter the log with it. It is never the cause + // for a true problem. + Some(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {} + _ => log::error!("{:?}", e), + }; + into_binder(e) + }) + } +} diff --git a/libs/rust/src/keymaster/security_level.rs b/libs/rust/src/keymaster/security_level.rs index f7f52f4..28c65d9 100644 --- a/libs/rust/src/keymaster/security_level.rs +++ b/libs/rust/src/keymaster/security_level.rs @@ -3,26 +3,23 @@ use std::time::SystemTime; use crate::{ android::{ hardware::security::keymint::{ - Algorithm::Algorithm, AttestationKey::AttestationKey, ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, KeyCreationResult::KeyCreationResult, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag + Algorithm::Algorithm, AttestationKey::AttestationKey, ErrorCode::ErrorCode, + HardwareAuthenticatorType::HardwareAuthenticatorType, IKeyMintDevice::IKeyMintDevice, + KeyCreationResult::KeyCreationResult, KeyFormat::KeyFormat, KeyParameter::KeyParameter, + KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, }, system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, - ResponseCode::ResponseCode, + AuthenticatorSpec::AuthenticatorSpec, CreateOperationResponse::CreateOperationResponse, Domain::Domain, EphemeralStorageKeyResponse::EphemeralStorageKeyResponse, IKeystoreOperation::IKeystoreOperation, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode }, }, err, - global::{DB, SUPER_KEY, UNDEFINED_NOT_AFTER}, + global::{DB, ENFORCEMENTS, SUPER_KEY, UNDEFINED_NOT_AFTER}, keymaster::{ - attestation_key_utils::{AttestationKeyInfo, get_attest_key_info}, - db::{ - BlobInfo, BlobMetaEntry, CertificateInfo, DateTime, DateTimeError, KeyIdGuard, - KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, Uuid, - }, - error::{KsError, map_binder_status}, - keymint_device::{KeyMintDevice, KeyMintWrapper, get_keymint_wrapper}, - super_key::{KeyBlob, SuperKeyManager}, - utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, + attestation_key_utils::{AttestationKeyInfo, get_attest_key_info}, db::{ + BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, Uuid + }, error::{KsError, into_logged_binder, map_binder_status}, keymint_device::get_keymint_wrapper, metrics_store::log_key_creation_event_stats, operation::{KeystoreOperation, LoggingInfo, OperationDb}, super_key::{KeyBlob, SuperKeyManager}, utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params} }, + plat::utils::multiuser_get_user_id, }; use crate::keymaster::key_parameter::KeyParameter as KsKeyParam; @@ -32,25 +29,27 @@ use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; use kmr_ta::HardwareInfo; -use rsbinder::{Strong, thread_state::CallingContext}; +use log::debug; +use rsbinder::{Interface, Status, thread_state::CallingContext}; + +// Blob of 32 zeroes used as empty masking key. +static ZERO_BLOB_32: &[u8] = &[0; 32]; pub struct KeystoreSecurityLevel<'a> { security_level: SecurityLevel, hw_info: HardwareInfo, km_uuid: Uuid, + operation_db: OperationDb, keymint: &'a dyn IKeyMintDevice, } impl<'a> KeystoreSecurityLevel<'a> { - pub fn new( - security_level: SecurityLevel, - hw_info: HardwareInfo, - km_uuid: Uuid, - ) -> Self { + pub fn new(security_level: SecurityLevel, hw_info: HardwareInfo, km_uuid: Uuid) -> Self { KeystoreSecurityLevel { security_level, hw_info, km_uuid, + operation_db: OperationDb::new(), keymint: get_keymint_wrapper(security_level).unwrap(), } } @@ -447,8 +446,543 @@ impl<'a> KeystoreSecurityLevel<'a> { _ => unreachable!(), // Other branches of get_attest_key_info are not possible here. }?; - let user_id = crate::plat::utils::uid_to_android_user(calling_uid); + let user_id = crate::plat::utils::multiuser_get_user_id(calling_uid); self.store_new_key(key, creation_result, user_id, Some(flags)) .context(err!()) } + + fn import_key( + &self, + key: &KeyDescriptor, + _attestation_key: Option<&KeyDescriptor>, + params: &[KeyParameter], + flags: i32, + key_data: &[u8], + ) -> Result { + if key.domain != Domain::BLOB && key.alias.is_none() { + return Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("Alias must be specified")); + } + + let calling_context = rsbinder::thread_state::CallingContext::default(); + let caller_uid = calling_context.uid; + + let key = match key.domain { + Domain::APP => KeyDescriptor { + domain: key.domain, + nspace: caller_uid as i64, + alias: key.alias.clone(), + blob: None, + }, + _ => key.clone(), + }; + + // import_key requires the rebind permission. + // check_key_permission(KeyPerm::Rebind, &key, &None).context(err!("In import_key."))?; + + let params = self + .add_required_parameters(caller_uid, params, &key) + .context(err!("Trying to get aaid."))?; + + let format = params + .iter() + .find(|p| p.tag == Tag::ALGORITHM) + .ok_or(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("No KeyParameter 'Algorithm'.")) + .and_then(|p| match &p.value { + KeyParameterValue::Algorithm(Algorithm::AES) + | KeyParameterValue::Algorithm(Algorithm::HMAC) + | KeyParameterValue::Algorithm(Algorithm::TRIPLE_DES) => Ok(KeyFormat::RAW), + KeyParameterValue::Algorithm(Algorithm::RSA) + | KeyParameterValue::Algorithm(Algorithm::EC) => Ok(KeyFormat::PKCS8), + v => Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("Unknown Algorithm {:?}.", v)), + }) + .context(err!())?; + + let km_dev = &self.keymint; + let creation_result = map_binder_status({ + let _wp = + self.watch("KeystoreSecurityLevel::import_key: calling IKeyMintDevice::importKey."); + km_dev.importKey(¶ms, format, key_data, None /* attestKey */) + }) + .context(err!("Trying to call importKey"))?; + + let user_id = multiuser_get_user_id(caller_uid); + self.store_new_key(key, creation_result, user_id, Some(flags)) + .context(err!()) + } + + fn import_wrapped_key( + &self, + key: &KeyDescriptor, + wrapping_key: &KeyDescriptor, + masking_key: Option<&[u8]>, + params: &[KeyParameter], + authenticators: &[AuthenticatorSpec], + ) -> Result { + let wrapped_data: &[u8] = match key { + KeyDescriptor { + domain: Domain::APP, + blob: Some(ref blob), + alias: Some(_), + .. + } + | KeyDescriptor { + domain: Domain::SELINUX, + blob: Some(ref blob), + alias: Some(_), + .. + } => blob, + _ => { + return Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)).context(err!( + "Alias and blob must be specified and domain must be APP or SELINUX. {:?}", + key + )); + } + }; + + if wrapping_key.domain == Domain::BLOB { + return Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)).context(err!( + "Import wrapped key not supported for self managed blobs." + )); + } + + let calling_context = rsbinder::thread_state::CallingContext::default(); + let caller_uid = calling_context.uid; + let user_id = multiuser_get_user_id(caller_uid); + + let key = match key.domain { + Domain::APP => KeyDescriptor { + domain: key.domain, + nspace: caller_uid as i64, + alias: key.alias.clone(), + blob: None, + }, + Domain::SELINUX => KeyDescriptor { + domain: Domain::SELINUX, + nspace: key.nspace, + alias: key.alias.clone(), + blob: None, + }, + _ => panic!("Unreachable."), + }; + + // Import_wrapped_key requires the rebind permission for the new key. + // check_key_permission(KeyPerm::Rebind, &key, &None).context(err!())?; + + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(user_id); + + let (wrapping_key_id_guard, mut wrapping_key_entry) = DB + .with(|db| { + db.borrow_mut().load_key_entry( + wrapping_key, + KeyType::Client, + KeyEntryLoadBits::KM, + caller_uid, + ) + }) + .context(err!("Failed to load wrapping key."))?; + + let (wrapping_key_blob, wrapping_blob_metadata) = wrapping_key_entry + .take_key_blob_info() + .ok_or_else(KsError::sys) + .context(err!( + "No km_blob after successfully loading key. This should never happen." + ))?; + + let wrapping_key_blob = SUPER_KEY + .read() + .unwrap() + .unwrap_key_if_required(&wrapping_blob_metadata, &wrapping_key_blob) + .context(err!("Failed to handle super encryption for wrapping key."))?; + + // km_dev.importWrappedKey does not return a certificate chain. + // TODO Do we assume that all wrapped keys are symmetric? + // let certificate_chain: Vec = Default::default(); + + let pw_sid = authenticators + .iter() + .find_map(|a| match a.authenticatorType { + HardwareAuthenticatorType::PASSWORD => Some(a.authenticatorId), + _ => None, + }) + .unwrap_or(-1); + + let fp_sid = authenticators + .iter() + .find_map(|a| match a.authenticatorType { + HardwareAuthenticatorType::FINGERPRINT => Some(a.authenticatorId), + _ => None, + }) + .unwrap_or(-1); + + let masking_key = masking_key.unwrap_or(ZERO_BLOB_32); + + let (creation_result, _) = self + .upgrade_keyblob_if_required_with( + Some(wrapping_key_id_guard), + &wrapping_key_blob, + wrapping_blob_metadata.km_uuid().copied(), + &[], + |wrapping_blob| { + let _wp = self.watch( + "KeystoreSecurityLevel::import_wrapped_key: calling IKeyMintDevice::importWrappedKey.", + ); + let creation_result = map_binder_status(self.keymint.importWrappedKey( + wrapped_data, + wrapping_blob, + masking_key, + params, + pw_sid, + fp_sid, + ))?; + Ok(creation_result) + }, + ) + .context(err!())?; + + self.store_new_key(key, creation_result, user_id, None) + .context(err!("Trying to store the new key.")) + } + + fn convert_storage_key_to_ephemeral( + &self, + storage_key: &KeyDescriptor, + ) -> Result { + if storage_key.domain != Domain::BLOB { + return Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("Key must be of Domain::BLOB")); + } + let key_blob = storage_key + .blob + .as_ref() + .ok_or(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("No key blob specified"))?; + + // convert_storage_key_to_ephemeral requires the associated permission + // check_key_permission(KeyPerm::ConvertStorageKeyToEphemeral, storage_key, &None) + // .context(err!("Check permission"))?; + + let km_dev = &self.keymint; + let res = { + let _wp = self.watch(concat!( + "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", + "calling IKeyMintDevice::convertStorageKeyToEphemeral (1)" + )); + map_binder_status(km_dev.convertStorageKeyToEphemeral(key_blob)) + }; + match res { + Ok(result) => Ok(EphemeralStorageKeyResponse { + ephemeralKey: result, + upgradedBlob: None, + }), + Err(KsError::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => { + let upgraded_blob = { + let _wp = self.watch("IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: calling IKeyMintDevice::upgradeKey"); + map_binder_status(km_dev.upgradeKey(key_blob, &[])) + } + .context(err!("Failed to upgrade key blob."))?; + let ephemeral_key = { + let _wp = self.watch(concat!( + "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", + "calling IKeyMintDevice::convertStorageKeyToEphemeral (2)" + )); + map_binder_status(km_dev.convertStorageKeyToEphemeral(&upgraded_blob)) + } + .context(err!("Failed to retrieve ephemeral key (after upgrade)."))?; + Ok(EphemeralStorageKeyResponse { + ephemeralKey: ephemeral_key, + upgradedBlob: Some(upgraded_blob), + }) + } + Err(e) => Err(e).context(err!("Failed to retrieve ephemeral key.")), + } + } + + fn create_operation( + &self, + key: &KeyDescriptor, + operation_parameters: &[KeyParameter], + forced: bool, + ) -> Result { + let caller_uid = CallingContext::default().uid; + // We use `scoping_blob` to extend the life cycle of the blob loaded from the database, + // so that we can use it by reference like the blob provided by the key descriptor. + // Otherwise, we would have to clone the blob from the key descriptor. + let scoping_blob: Vec; + let (km_blob, key_properties, key_id_guard, blob_metadata) = match key.domain { + Domain::BLOB => { + // check_key_permission(KeyPerm::Use, key, &None) + // .context(err!("checking use permission for Domain::BLOB."))?; + // if forced { + // check_key_permission(KeyPerm::ReqForcedOp, key, &None) + // .context(err!("checking forced permission for Domain::BLOB."))?; + // } + ( + match &key.blob { + Some(blob) => blob, + None => { + return Err(KsError::sys()).context(err!( + "Key blob must be specified when \ + using Domain::BLOB." + )); + } + }, + None, + None, + BlobMetaData::new(), + ) + } + _ => { + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(multiuser_get_user_id(caller_uid)); + let (key_id_guard, mut key_entry) = DB + .with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| { + db.borrow_mut().load_key_entry( + key, + KeyType::Client, + KeyEntryLoadBits::KM, + caller_uid, + // |k, av| { + // check_key_permission(KeyPerm::Use, k, &av)?; + // if forced { + // check_key_permission(KeyPerm::ReqForcedOp, k, &av)?; + // } + // Ok(()) + // }, + ) + }) + .context(err!("Failed to load key blob."))?; + + let (blob, blob_metadata) = + key_entry.take_key_blob_info().ok_or_else(KsError::sys).context(err!( + "Successfully loaded key entry, \ + but KM blob was missing." + ))?; + scoping_blob = blob; + + ( + &scoping_blob, + Some((key_id_guard.id(), key_entry.into_key_parameters())), + Some(key_id_guard), + blob_metadata, + ) + } + }; + + let purpose = operation_parameters.iter().find(|p| p.tag == Tag::PURPOSE).map_or( + Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("No operation purpose specified.")), + |kp| match kp.value { + KeyParameterValue::KeyPurpose(p) => Ok(p), + _ => Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("Malformed KeyParameter.")), + }, + )?; + + // Remove Tag::PURPOSE from the operation_parameters, since some keymaster devices return + // an error on begin() if Tag::PURPOSE is in the operation_parameters. + let op_params: Vec = + operation_parameters.iter().filter(|p| p.tag != Tag::PURPOSE).cloned().collect(); + let operation_parameters = op_params.as_slice(); + + let (immediate_hat, mut auth_info) = ENFORCEMENTS + .authorize_create( + purpose, + key_properties.as_ref(), + operation_parameters.as_ref(), + false, + ) + .context(err!())?; + + let km_blob = SUPER_KEY + .read() + .unwrap() + .unwrap_key_if_required(&blob_metadata, km_blob) + .context(err!("Failed to handle super encryption."))?; + + let (begin_result, upgraded_blob) = self + .upgrade_keyblob_if_required_with( + key_id_guard, + &km_blob, + blob_metadata.km_uuid().copied(), + operation_parameters, + |blob| loop { + match map_binder_status({ + let _wp = self.watch( + "KeystoreSecurityLevel::create_operation: calling IKeyMintDevice::begin", + ); + self.keymint.begin( + purpose, + blob, + operation_parameters, + immediate_hat.as_ref(), + ) + }) { + Err(KsError::Km(ErrorCode::TOO_MANY_OPERATIONS)) => { + self.operation_db.prune(caller_uid, forced)?; + continue; + } + v @ Err(KsError::Km(ErrorCode::INVALID_KEY_BLOB)) => { + if let Some((key_id, _)) = key_properties { + if let Ok(Some(key)) = + DB.with(|db| db.borrow_mut().load_key_descriptor(key_id)) + { + log::error!("Key integrity violation detected for key id {}", key_id); + } else { + log::error!("Failed to load key descriptor for audit log"); + } + } + return v; + } + v => return v, + } + }, + ) + .context(err!("Failed to begin operation."))?; + + let operation_challenge = auth_info.finalize_create_authorization(begin_result.challenge); + + let op_params: Vec = operation_parameters.to_vec(); + + let operation = match begin_result.operation { + Some(km_op) => self.operation_db.create_operation( + km_op, + caller_uid, + auth_info, + forced, + LoggingInfo::new(self.security_level, purpose, op_params, upgraded_blob.is_some()), + ), + None => { + return Err(KsError::sys()).context(err!( + "Begin operation returned successfully, \ + but did not return a valid operation." + )); + } + }; + + let op_binder: rsbinder::Strong = + KeystoreOperation::new_native_binder(operation) + .as_binder() + .into_interface() + .context(err!("Failed to create IKeystoreOperation."))?; + + Ok(CreateOperationResponse { + iOperation: Some(op_binder), + operationChallenge: operation_challenge, + parameters: match begin_result.params.len() { + 0 => None, + _ => Some(KeyParameters { keyParameter: begin_result.params }), + }, + // An upgraded blob should only be returned if the caller has permission + // to use Domain::BLOB keys. If we got to this point, we already checked + // that the caller had that permission. + upgradedBlob: if key.domain == Domain::BLOB { upgraded_blob } else { None }, + }) + } + + fn delete_key(&self, key: &KeyDescriptor) -> Result<()> { + if key.domain != Domain::BLOB { + return Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("delete_key: Key must be of Domain::BLOB")); + } + + let key_blob = key + .blob + .as_ref() + .ok_or(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("delete_key: No key blob specified"))?; + + // check_key_permission(KeyPerm::Delete, key, &None) + // .context(err!("delete_key: Checking delete permissions"))?; + + let km_dev = &self.keymint; + { + let _wp = + self.watch("KeystoreSecuritylevel::delete_key: calling IKeyMintDevice::deleteKey"); + map_binder_status(km_dev.deleteKey(key_blob)).context(err!("keymint device deleteKey")) + } + } +} + +impl<'a> Interface for KeystoreSecurityLevel<'a> {} + +impl<'a> IKeystoreSecurityLevel for KeystoreSecurityLevel<'a> { + fn createOperation( + &self, + key: &KeyDescriptor, + operation_parameters: &[KeyParameter], + forced: bool, + ) -> Result { + let _wp = self.watch("IKeystoreSecurityLevel::createOperation"); + Ok(self.create_operation(key, operation_parameters, forced) + .map_err(into_logged_binder)?) + } + + fn generateKey( + &self, + key: &KeyDescriptor, + attestation_key: Option<&KeyDescriptor>, + params: &[KeyParameter], + flags: i32, + entropy: &[u8], + ) -> Result { + // Duration is set to 5 seconds, because generateKey - especially for RSA keys, takes more + // time than other operations + let _wp = self.watch_millis("IKeystoreSecurityLevel::generateKey", 5000); + let result = self.generate_key(key, attestation_key, params, flags, entropy); + log_key_creation_event_stats(self.security_level, params, &result); + debug!("generateKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + result.map_err(into_logged_binder) + } + + fn importKey( + &self, + key: &KeyDescriptor, + attestation_key: Option<&KeyDescriptor>, + params: &[KeyParameter], + flags: i32, + key_data: &[u8], + ) -> Result { + let _wp = self.watch("IKeystoreSecurityLevel::importKey"); + let result = self.import_key(key, attestation_key, params, flags, key_data); + log_key_creation_event_stats(self.security_level, params, &result); + debug!("importKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + result.map_err(into_logged_binder) + } + fn importWrappedKey( + &self, + key: &KeyDescriptor, + wrapping_key: &KeyDescriptor, + masking_key: Option<&[u8]>, + params: &[KeyParameter], + authenticators: &[AuthenticatorSpec], + ) -> Result { + let _wp = self.watch("IKeystoreSecurityLevel::importWrappedKey"); + let result = + self.import_wrapped_key(key, wrapping_key, masking_key, params, authenticators); + log_key_creation_event_stats(self.security_level, params, &result); + debug!("importWrappedKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + result.map_err(into_logged_binder) + } + fn convertStorageKeyToEphemeral( + &self, + storage_key: &KeyDescriptor, + ) -> Result { + let _wp = self.watch("IKeystoreSecurityLevel::convertStorageKeyToEphemeral"); + self.convert_storage_key_to_ephemeral(storage_key) + .map_err(into_logged_binder) + } + fn deleteKey(&self, key: &KeyDescriptor) -> Result<(), Status> { + let _wp = self.watch("IKeystoreSecurityLevel::deleteKey"); + let result = self.delete_key(key); + debug!("deleteKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + result.map_err(into_logged_binder) + } } diff --git a/libs/rust/src/keymaster/super_key.rs b/libs/rust/src/keymaster/super_key.rs index a7a089c..75757ce 100644 --- a/libs/rust/src/keymaster/super_key.rs +++ b/libs/rust/src/keymaster/super_key.rs @@ -162,7 +162,7 @@ pub struct SuperKey { reencrypt_with: Option>, } -trait AesGcm { +pub trait AesGcm { fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result; fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec, Vec, Vec)>; } diff --git a/libs/rust/src/keymaster/utils.rs b/libs/rust/src/keymaster/utils.rs index 5006e51..a75d62a 100644 --- a/libs/rust/src/keymaster/utils.rs +++ b/libs/rust/src/keymaster/utils.rs @@ -1,14 +1,21 @@ use crate::{ android::hardware::security::keymint::{ ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, - KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, KeyParameterValue::KeyParameterValue, - }, consts, err, keymaster::{ + KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, + KeyParameterValue::KeyParameterValue, + }, + consts, err, + keymaster::{ error::KsError as Error, key_parameter::KeyParameter, keymint_device::KeyMintDevice, - }, watchdog + }, + watchdog, }; -use anyhow::{anyhow, Context, Result, Ok}; -use kmr_wire::{keymint::{self, KeyParam}, KeySizeInBits}; +use anyhow::{anyhow, Context, Ok, Result}; +use kmr_wire::{ + keymint::{self, KeyParam}, + KeySizeInBits, +}; pub fn log_params(params: &[KmKeyParameter]) -> Vec { params.iter().cloned().collect::>() @@ -243,7 +250,9 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { _ => return Err(anyhow!("Mismatched key parameter value type")), }?; - Ok(KeyParam::RsaPublicExponent(kmr_wire::RsaExponent(value as u64))) + Ok(KeyParam::RsaPublicExponent(kmr_wire::RsaExponent( + value as u64, + ))) } keymint::Tag::IncludeUniqueId => Ok(KeyParam::IncludeUniqueId), keymint::Tag::RsaOaepMgfDigest => { @@ -493,7 +502,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::VendorPatchlevel(value as u32)) - }, + } keymint::Tag::BootPatchlevel => { let value = match value { KeyParameterValue::Integer(v) => Ok(v), @@ -501,7 +510,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::BootPatchlevel(value as u32)) - }, + } keymint::Tag::DeviceUniqueAttestation => Ok(KeyParam::DeviceUniqueAttestation), keymint::Tag::IdentityCredentialKey => Err(anyhow!(err!("Not implemented"))), keymint::Tag::StorageKey => Ok(KeyParam::StorageKey), @@ -521,7 +530,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::Nonce(value)) - }, + } keymint::Tag::MacLength => { let value = match value { KeyParameterValue::Integer(v) => Ok(v), @@ -529,7 +538,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::MacLength(value as u32)) - }, + } keymint::Tag::ResetSinceIdRotation => Ok(KeyParam::ResetSinceIdRotation), keymint::Tag::ConfirmationToken => Err(anyhow!(err!("Not implemented"))), keymint::Tag::CertificateSerial => { @@ -539,7 +548,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::CertificateSerial(value)) - }, + } keymint::Tag::CertificateSubject => { let value = match value { KeyParameterValue::Blob(v) => Ok(v), @@ -547,17 +556,19 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::CertificateSubject(value)) - }, + } keymint::Tag::CertificateNotBefore => { let value = match value { KeyParameterValue::DateTime(v) => Ok(v), _ => return Err(anyhow!("Mismatched key parameter value type")), }?; - Ok(KeyParam::CertificateNotBefore(kmr_wire::keymint::DateTime { - ms_since_epoch: value, - })) - }, + Ok(KeyParam::CertificateNotBefore( + kmr_wire::keymint::DateTime { + ms_since_epoch: value, + }, + )) + } keymint::Tag::CertificateNotAfter => { let value = match value { KeyParameterValue::DateTime(v) => Ok(v), @@ -567,7 +578,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { Ok(KeyParam::CertificateNotAfter(kmr_wire::keymint::DateTime { ms_since_epoch: value, })) - }, + } keymint::Tag::MaxBootLevel => { let value = match value { KeyParameterValue::Integer(v) => Ok(v), @@ -575,7 +586,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::MaxBootLevel(value as u32)) - }, + } keymint::Tag::ModuleHash => { let value = match value { KeyParameterValue::Blob(v) => Ok(v), @@ -583,7 +594,7 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { }?; Ok(KeyParam::ModuleHash(value)) - }, + } } } } diff --git a/libs/rust/src/keymint/sdd.rs b/libs/rust/src/keymint/sdd.rs index 25ce988..e2730e3 100644 --- a/libs/rust/src/keymint/sdd.rs +++ b/libs/rust/src/keymint/sdd.rs @@ -27,6 +27,7 @@ use std::io::BufRead; use std::io::Write; use std::path; +#[cfg(not(target_os = "android"))] const SECURE_DELETION_DATA_FILE: &str = "./omk/data/keymint_secure_deletion_data"; #[cfg(target_os = "android")] diff --git a/libs/rust/src/lib.rs b/libs/rust/src/lib.rs index bf747e5..236eb0b 100644 --- a/libs/rust/src/lib.rs +++ b/libs/rust/src/lib.rs @@ -3,10 +3,13 @@ use anyhow::Context; use lazy_static; -use std::sync::{Arc, Mutex}; +use std::{ + panic, + sync::{Arc, Mutex}, +}; use jni::JavaVM; -use log::debug; +use log::{debug, error}; pub mod consts; pub mod global; @@ -43,6 +46,11 @@ pub extern "C" fn JNI_OnLoad( rsbinder::ProcessState::init_default(); debug!("Hello, OhMyKeymint!"); + // Redirect panic messages to logcat. + panic::set_hook(Box::new(|panic_info| { + error!("{}", panic_info); + })); + let class = env .find_class("top/qwq2333/ohmykeymint/Native") .context("Failed to find class top/qwq2333/ohmykeymint/Native") diff --git a/libs/rust/src/logging.rs b/libs/rust/src/logging.rs index 7c2cb7e..7f4eb44 100644 --- a/libs/rust/src/logging.rs +++ b/libs/rust/src/logging.rs @@ -5,6 +5,7 @@ use log4rs::encode::pattern::PatternEncoder; const PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S %Z)(utc)} [{h({l})}] {M} - {m}{n}"; +#[cfg(not(target_os = "android"))] pub fn init_logger() { let stdout = ConsoleAppender::builder() .encoder(Box::new(PatternEncoder::new(PATTERN))) @@ -17,13 +18,11 @@ pub fn init_logger() { log4rs::init_config(config).unwrap(); } -#[cfg(target_os = "android")] -use android_logger::Config; #[cfg(target_os = "android")] pub fn init_logger() { android_logger::init_once( - Config::default() + android_logger::Config::default() .with_max_level(LevelFilter::Trace) .with_tag("OhMyKeymint"), ); diff --git a/libs/rust/src/main.rs b/libs/rust/src/main.rs index 5a249d7..dff0abe 100644 --- a/libs/rust/src/main.rs +++ b/libs/rust/src/main.rs @@ -1,7 +1,7 @@ #![recursion_limit = "256"] #![feature(once_cell_get_mut)] -use std::io::Read; +use std::{io::Read, panic}; use kmr_common::crypto::{self, MonotonicClock}; use kmr_crypto_boring::{ @@ -51,10 +51,13 @@ fn main() { let db = keymaster::db::KeymasterDb::new().unwrap(); db.close().unwrap(); + // Redirect panic messages to logcat. + panic::set_hook(Box::new(|panic_info| { + error!("{}", panic_info); + })); + debug!("Hello, OhMyKeymint!"); - #[cfg(target_os = "android")] - logi!(TAG, "Application started"); let security_level: SecurityLevel = SecurityLevel::TrustedEnvironment; let hw_info = HardwareInfo { version_number: 2, diff --git a/libs/rust/src/plat/property_watcher.rs b/libs/rust/src/plat/property_watcher.rs index b7e380f..da1c2f8 100644 --- a/libs/rust/src/plat/property_watcher.rs +++ b/libs/rust/src/plat/property_watcher.rs @@ -1,5 +1,7 @@ use log::info; use rsproperties::PropertyConfig; + +#[cfg(not(target_os = "android"))] use rsproperties_service; use anyhow::{anyhow, Context, Ok, Result}; @@ -12,19 +14,19 @@ pub struct PropertyWatcher { } impl PropertyWatcher { - #[cfg(target_os = "linux")] + #[cfg(not(target_os = "android"))] pub fn new(name: &str) -> anyhow::Result { HAS_INIT.call_once(|| { - init(); + init().unwrap(); }); Ok(PropertyWatcher { name: name.to_string(), }) } - #[cfg(not(target_os = "linux"))] + #[cfg(target_os = "android")] pub fn new(name: &str) -> anyhow::Result { - Ok(PropertyWatcher { name }) + Ok(PropertyWatcher { name: name.to_string() }) } pub fn read(&self) -> Result { @@ -56,6 +58,7 @@ impl PropertyWatcher { } #[tokio::main] +#[cfg(not(target_os = "android"))] async fn init() -> Result<()> { std::fs::create_dir_all("./omk/properties").unwrap(); std::fs::create_dir_all("./omk/property_socket").unwrap(); From 2d28be7f2a5de04b68e1f5eea16f97873794d923 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 11 Oct 2025 03:51:34 +0800 Subject: [PATCH 09/46] prepare for aarch64-linux-android Signed-off-by: qwq233 --- libs/rust/.gitignore | 1 + libs/rust/boringssl/Cargo.toml | 11 +- libs/rust/boringssl/src/aes_cmac.rs | 6 + libs/rust/boringssl/src/hmac.rs | 13 +++ libs/rust/boringssl/src/km.rs | 16 +++ libs/rust/src/keymaster/db.rs | 45 +++++--- libs/rust/src/keymaster/metrics_store.rs | 80 ++++++++----- libs/rust/src/keymaster/mod.rs | 2 +- libs/rust/src/keymaster/operation.rs | 130 +++++++++++++++++----- libs/rust/src/keymaster/security_level.rs | 127 ++++++++++++++------- libs/rust/src/logging.rs | 1 - libs/rust/src/plat/property_watcher.rs | 4 +- 12 files changed, 322 insertions(+), 114 deletions(-) diff --git a/libs/rust/.gitignore b/libs/rust/.gitignore index fcc1585..32e25f3 100644 --- a/libs/rust/.gitignore +++ b/libs/rust/.gitignore @@ -6,6 +6,7 @@ target # will have compiled files and executables debug/ target/ +build/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/libs/rust/boringssl/Cargo.toml b/libs/rust/boringssl/Cargo.toml index 8e413ff..6c379e1 100644 --- a/libs/rust/boringssl/Cargo.toml +++ b/libs/rust/boringssl/Cargo.toml @@ -9,19 +9,22 @@ edition = "2021" license = "Apache-2.0" [dependencies] -ffi = { package = "openssl-sys", version = "^0.9.75", features = ["bindgen"] } foreign-types = "0.3.1" kmr-common = "*" kmr-wire = "*" libc = "^0.2.112" log = "^0.4" -openssl = "^0.10.36" anyhow = "*" thiserror = "*" nix = "*" -[target.'cfg(android)'.dependencies] -boringssl = "0.0.5" +[target.'cfg(target_os = "linux")'.dependencies] +openssl = "^0.10.36" +ffi = { package = "openssl-sys", version = "^0.9.75", features = ["bindgen"] } + +[target.'cfg(target_os = "android")'.dependencies] +openssl = { package = "openssl", version = "*", features = ["unstable_boringssl", "bindgen"] } +ffi = { package = "openssl-sys", version = "^0.9.75", features = ["bindgen"] } [dev-dependencies] kmr-tests = "*" diff --git a/libs/rust/boringssl/src/aes_cmac.rs b/libs/rust/boringssl/src/aes_cmac.rs index 063ae39..37bef9c 100644 --- a/libs/rust/boringssl/src/aes_cmac.rs +++ b/libs/rust/boringssl/src/aes_cmac.rs @@ -92,9 +92,15 @@ impl core::ops::Drop for BoringAesCmacOperation { impl crypto::AccumulatingOperation for BoringAesCmacOperation { fn update(&mut self, data: &[u8]) -> Result<(), Error> { // Safety: `self.ctx` is non-null and valid, and `data` is a valid slice. + + #[cfg(not(target_os = "android"))] let result = unsafe { ffi::CMAC_Update(self.ctx.0, data.as_ptr() as *const libc::c_void, data.len()) }; + + #[cfg(target_os = "android")] + let result = unsafe { ffi::CMAC_Update(self.ctx.0, data.as_ptr(), data.len()) }; + if result != 1 { return Err(openssl_last_err()); } diff --git a/libs/rust/boringssl/src/hmac.rs b/libs/rust/boringssl/src/hmac.rs index d6df2b2..e644da7 100644 --- a/libs/rust/boringssl/src/hmac.rs +++ b/libs/rust/boringssl/src/hmac.rs @@ -52,6 +52,7 @@ impl crypto::Hmac for BoringHmac { // Safety: `op.ctx` is known non-null and valid, as is the result of // `digest_into_openssl_ffi()`. `key_len` is length of `key.0`, which is a valid `Vec`. + #[cfg(not(target_os = "android"))] let result = unsafe { ffi::HMAC_Init_ex( op.ctx.0, @@ -61,6 +62,18 @@ impl crypto::Hmac for BoringHmac { core::ptr::null_mut(), ) }; + + #[cfg(target_os = "android")] + let result = unsafe { + ffi::HMAC_Init_ex( + op.ctx.0, + key.0.as_ptr() as *const libc::c_void, + key_len as usize, + digest, + core::ptr::null_mut(), + ) + }; + if result != 1 { error!("Failed to HMAC_Init_ex()"); return Err(openssl_last_err()); diff --git a/libs/rust/boringssl/src/km.rs b/libs/rust/boringssl/src/km.rs index 3b65d9d..8ad73de 100644 --- a/libs/rust/boringssl/src/km.rs +++ b/libs/rust/boringssl/src/km.rs @@ -76,6 +76,8 @@ pub fn hmac_sha256(key: &[u8], msg: &[u8]) -> Result, Error> { // pair. let digest = unsafe { ffi::EVP_sha256() }; let mut out_len = tag.len() as u32; + + #[cfg(not(target_os = "android"))] let result = unsafe { ffi::HMAC( digest, @@ -87,6 +89,20 @@ pub fn hmac_sha256(key: &[u8], msg: &[u8]) -> Result, Error> { &mut out_len, ) }; + + #[cfg(target_os = "android")] + let result = unsafe { + ffi::HMAC( + digest, + key.as_ptr() as *const std::ffi::c_void, + key.len(), + msg.as_ptr(), + msg.len(), + tag.as_mut_ptr(), + &mut out_len, + ) + }; + if !result.is_null() { Ok(tag) } else { diff --git a/libs/rust/src/keymaster/db.rs b/libs/rust/src/keymaster/db.rs index 3ffbe58..8e8953e 100644 --- a/libs/rust/src/keymaster/db.rs +++ b/libs/rust/src/keymaster/db.rs @@ -9,11 +9,16 @@ use std::{ use anyhow::{anyhow, Context, Result}; use rand::random; use rusqlite::{ - Connection, OptionalExtension, ToSql, Transaction, params, params_from_iter, types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef} + params, params_from_iter, + types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef}, + Connection, OptionalExtension, ToSql, Transaction, }; use crate::{ - android::{hardware::security::keymint::ErrorCode::ErrorCode, security::metrics::{Storage::Storage as MetricsStorage, StorageStats::StorageStats}}, + android::{ + hardware::security::keymint::ErrorCode::ErrorCode, + security::metrics::{Storage::Storage as MetricsStorage, StorageStats::StorageStats}, + }, keymaster::{database::perboot::PerbootDB, super_key::SuperKeyType}, plat::utils::AID_USER_OFFSET, watchdog as wd, @@ -61,7 +66,8 @@ use crate::{ android::{ hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, - HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel, Tag::Tag, + HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel, + Tag::Tag, }, system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, @@ -1272,12 +1278,21 @@ impl KeymasterDb { params: &[&str], ) -> Result { let (total, unused) = self.with_transaction(TransactionBehavior::Deferred, |tx| { - tx.query_row(query, params_from_iter(params), |row| Ok((row.get(0)?, row.get(1)?))) - .with_context(|| { - err!("get_storage_stat: Error size of storage type {}", storage_type.0) - }) + tx.query_row(query, params_from_iter(params), |row| { + Ok((row.get(0)?, row.get(1)?)) + }) + .with_context(|| { + err!( + "get_storage_stat: Error size of storage type {}", + storage_type.0 + ) + }) })?; - Ok(StorageStats { storage_type, size: total, unused_size: unused }) + Ok(StorageStats { + storage_type, + size: total, + unused_size: unused, + }) } fn get_total_size(&mut self) -> Result { @@ -1305,7 +1320,6 @@ impl KeymasterDb { ) } - /// Fetches a storage statistics atom for a given storage type. For storage /// types that map to a table, information about the table's storage is /// returned. Requests for storage types that are not DB tables return None. @@ -1320,9 +1334,11 @@ impl KeymasterDb { MetricsStorage::KEY_ENTRY_ID_INDEX => { self.get_table_size(storage_type, "persistent", "keyentry_id_index") } - MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX => { - self.get_table_size(storage_type, "persistent", "keyentry_domain_namespace_index") - } + MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX => self.get_table_size( + storage_type, + "persistent", + "keyentry_domain_namespace_index", + ), MetricsStorage::BLOB_ENTRY => { self.get_table_size(storage_type, "persistent", "blobentry") } @@ -1359,7 +1375,10 @@ impl KeymasterDb { MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX => { self.get_table_size(storage_type, "persistent", "blobmetadata_blobentryid_index") } - _ => Err(anyhow::Error::msg(format!("Unsupported storage type: {}", storage_type.0))), + _ => Err(anyhow::Error::msg(format!( + "Unsupported storage type: {}", + storage_type.0 + ))), } } diff --git a/libs/rust/src/keymaster/metrics_store.rs b/libs/rust/src/keymaster/metrics_store.rs index 389ffb1..d840571 100644 --- a/libs/rust/src/keymaster/metrics_store.rs +++ b/libs/rust/src/keymaster/metrics_store.rs @@ -17,12 +17,6 @@ //! stores them in an in-memory store. //! 2. Returns the collected metrics when requested by the statsd proxy. -use crate::keymaster::error::anyhow_error_to_serialized_error; -use crate::global::DB; -use crate::keymaster::key_parameter::KeyParameterValue as KsKeyParamValue; -use crate::err; -use crate::keymaster::operation::Outcome; -use crate::watchdog as wd; use crate::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin, @@ -44,6 +38,12 @@ use crate::android::security::metrics::{ RkpError::RkpError as MetricsRkpError, RkpErrorStats::RkpErrorStats, SecurityLevel::SecurityLevel as MetricsSecurityLevel, Storage::Storage as MetricsStorage, }; +use crate::err; +use crate::global::DB; +use crate::keymaster::error::anyhow_error_to_serialized_error; +use crate::keymaster::key_parameter::KeyParameterValue as KsKeyParamValue; +use crate::keymaster::operation::Outcome; +use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; use std::collections::HashMap; use std::sync::{LazyLock, Mutex}; @@ -124,12 +124,17 @@ impl MetricsStore { } let metrics_store_guard = self.metrics_store.lock().unwrap(); - metrics_store_guard.get(&atom_id).map_or(Ok(Vec::::new()), |atom_count_map| { - Ok(atom_count_map - .iter() - .map(|(atom, count)| KeystoreAtom { payload: atom.clone(), count: *count }) - .collect()) - }) + metrics_store_guard + .get(&atom_id) + .map_or(Ok(Vec::::new()), |atom_count_map| { + Ok(atom_count_map + .iter() + .map(|(atom, count)| KeystoreAtom { + payload: atom.clone(), + count: *count, + }) + .collect()) + }) } /// Insert an atom object to the metrics_store indexed by the atom ID. @@ -141,13 +146,16 @@ impl MetricsStore { *atom_count += 1; } else { // Insert an overflow atom - let overflow_atom_count_map = - metrics_store_guard.entry(AtomID::KEYSTORE2_ATOM_WITH_OVERFLOW).or_default(); + let overflow_atom_count_map = metrics_store_guard + .entry(AtomID::KEYSTORE2_ATOM_WITH_OVERFLOW) + .or_default(); if overflow_atom_count_map.len() < MetricsStore::SINGLE_ATOM_STORE_MAX_SIZE { let overflow_atom = Keystore2AtomWithOverflow { atom_id }; let atom_count = overflow_atom_count_map - .entry(KeystoreAtomPayload::Keystore2AtomWithOverflow(overflow_atom)) + .entry(KeystoreAtomPayload::Keystore2AtomWithOverflow( + overflow_atom, + )) .or_insert(0); *atom_count += 1; } else { @@ -170,9 +178,14 @@ pub fn log_key_creation_event_stats( key_creation_with_purpose_and_modes_info, ) = process_key_creation_event_stats(sec_level, key_params, result); - METRICS_STORE - .insert_atom(AtomID::KEY_CREATION_WITH_GENERAL_INFO, key_creation_with_general_info); - METRICS_STORE.insert_atom(AtomID::KEY_CREATION_WITH_AUTH_INFO, key_creation_with_auth_info); + METRICS_STORE.insert_atom( + AtomID::KEY_CREATION_WITH_GENERAL_INFO, + key_creation_with_general_info, + ); + METRICS_STORE.insert_atom( + AtomID::KEY_CREATION_WITH_AUTH_INFO, + key_creation_with_auth_info, + ); METRICS_STORE.insert_atom( AtomID::KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO, key_creation_with_purpose_and_modes_info, @@ -186,7 +199,11 @@ fn process_key_creation_event_stats( sec_level: SecurityLevel, key_params: &[KeyParameter], result: &Result, -) -> (KeystoreAtomPayload, KeystoreAtomPayload, KeystoreAtomPayload) { +) -> ( + KeystoreAtomPayload, + KeystoreAtomPayload, + KeystoreAtomPayload, +) { // In the default atom objects, fields represented by bitmaps and i32 fields // will take 0, except error_code which defaults to 1 indicating NO_ERROR and key_size, // and auth_time_out which defaults to -1. @@ -337,8 +354,10 @@ pub fn log_key_operation_event_stats( op_outcome, key_upgraded, ); - METRICS_STORE - .insert_atom(AtomID::KEY_OPERATION_WITH_GENERAL_INFO, key_operation_with_general_info); + METRICS_STORE.insert_atom( + AtomID::KEY_OPERATION_WITH_GENERAL_INFO, + key_operation_with_general_info, + ); METRICS_STORE.insert_atom( AtomID::KEY_OPERATION_WITH_PURPOSE_AND_MODES_INFO, key_operation_with_purpose_and_modes_info, @@ -544,7 +563,10 @@ pub(crate) fn pull_storage_stats() -> Result> { ..Default::default() }), Err(error) => { - log::error!("pull_metrics_callback: Error getting storage stat: {}", error) + log::error!( + "pull_metrics_callback: Error getting storage stat: {}", + error + ) } }; }; @@ -598,9 +620,7 @@ pub fn update_keystore_crash_sysprop() { return; } }; - if let Err(e) = - rsproperties::set(KEYSTORE_CRASH_COUNT_PROPERTY, &new_count.to_string()) - { + if let Err(e) = rsproperties::set(KEYSTORE_CRASH_COUNT_PROPERTY, &new_count.to_string()) { log::error!( concat!( "In update_keystore_crash_sysprop:: ", @@ -613,7 +633,8 @@ pub fn update_keystore_crash_sysprop() { /// Read the system property: keystore.crash_count. pub fn read_keystore_crash_count() -> Result> { - let sp: std::result::Result = rsproperties::get(KEYSTORE_CRASH_COUNT_PROPERTY); + let sp: std::result::Result = + rsproperties::get(KEYSTORE_CRASH_COUNT_PROPERTY); match sp { Ok(count_str) => { let count = count_str.parse::()?; @@ -916,7 +937,12 @@ impl Summary for KeystoreAtomPayload { fn show(&self) -> String { match self { KeystoreAtomPayload::StorageStats(v) => { - format!("{} sz={} unused={}", v.storage_type.show(), v.size, v.unused_size) + format!( + "{} sz={} unused={}", + v.storage_type.show(), + v.size, + v.unused_size + ) } KeystoreAtomPayload::KeyCreationWithGeneralInfo(v) => { format!( diff --git a/libs/rust/src/keymaster/mod.rs b/libs/rust/src/keymaster/mod.rs index e1d2581..9f60db1 100644 --- a/libs/rust/src/keymaster/mod.rs +++ b/libs/rust/src/keymaster/mod.rs @@ -2,13 +2,13 @@ pub mod attestation_key_utils; pub mod boot_key; pub mod crypto; pub mod database; -pub mod metrics_store; pub mod db; pub mod enforcements; pub mod error; pub mod key_parameter; pub mod keymint_device; pub mod keymint_operation; +pub mod metrics_store; pub mod operation; pub mod permission; pub mod security_level; diff --git a/libs/rust/src/keymaster/operation.rs b/libs/rust/src/keymaster/operation.rs index a46dc76..f9ff127 100644 --- a/libs/rust/src/keymaster/operation.rs +++ b/libs/rust/src/keymaster/operation.rs @@ -130,11 +130,11 @@ use crate::android::hardware::security::keymint::ErrorCode::ErrorCode; use crate::android::system::keystore2::ResponseCode::ResponseCode; use crate::keymaster::enforcements::AuthInfo; -use crate::keymaster::error::{KsError as Error, SerializedError, error_to_serialized_error, into_binder, into_logged_binder, map_binder_status}; +use crate::keymaster::error::{ + error_to_serialized_error, into_binder, into_logged_binder, map_binder_status, + KsError as Error, SerializedError, +}; -use crate::err; -use crate::keymaster::metrics_store::log_key_operation_event_stats; -use crate::watchdog as wd; use crate::android::hardware::security::keymint::{ IKeyMintOperation::IKeyMintOperation, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, @@ -142,6 +142,9 @@ use crate::android::hardware::security::keymint::{ use crate::android::system::keystore2::{ IKeystoreOperation::BnKeystoreOperation, IKeystoreOperation::IKeystoreOperation, }; +use crate::err; +use crate::keymaster::metrics_store::log_key_operation_event_stats; +use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; use rsbinder::{Status, Strong}; use std::{ @@ -202,7 +205,12 @@ impl LoggingInfo { op_params: Vec, key_upgraded: bool, ) -> LoggingInfo { - Self { sec_level, purpose, op_params, key_upgraded } + Self { + sec_level, + purpose, + op_params, + key_upgraded, + } } } @@ -324,8 +332,10 @@ impl Operation { let guard = self.outcome.lock().expect("In check_active."); match *guard { Outcome::Unknown => Ok(guard), - _ => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) - .context(err!("Call on finalized operation with outcome: {:?}.", *guard)), + _ => Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)).context(err!( + "Call on finalized operation with outcome: {:?}.", + *guard + )), } } @@ -428,7 +438,11 @@ impl Operation { }) .context(err!("Finish failed."))?; - self.auth_info.lock().unwrap().after_finish().context("In finish.")?; + self.auth_info + .lock() + .unwrap() + .after_finish() + .context("In finish.")?; // At this point the operation concluded successfully. *outcome = Outcome::Success; @@ -487,7 +501,9 @@ pub struct OperationDb { impl OperationDb { /// Creates a new OperationDb. pub fn new() -> Self { - Self { operations: Mutex::new(Vec::new()) } + Self { + operations: Mutex::new(Vec::new()), + } } /// Creates a new operation. @@ -539,7 +555,11 @@ impl OperationDb { } fn get(&self, index: usize) -> Option> { - self.operations.lock().expect("In OperationDb::get.").get(index).and_then(|op| op.upgrade()) + self.operations + .lock() + .expect("In OperationDb::get.") + .get(index) + .and_then(|op| op.upgrade()) } /// Attempts to prune an operation. @@ -640,7 +660,11 @@ impl OperationDb { }); // If the operation is forced, the caller has a malus of 0. - let caller_malus = if forced { 0 } else { 1u64 + *owners.entry(caller).or_default() }; + let caller_malus = if forced { + 0 + } else { + 1u64 + *owners.entry(caller).or_default() + }; // We iterate through all operations computing the malus and finding // the candidate with the highest malus which must also be higher @@ -654,7 +678,13 @@ impl OperationDb { let mut oldest_caller_op: Option = None; let candidate = pruning_info.iter().fold( None, - |acc: Option, &PruningInfo { last_usage, owner, index, forced }| { + |acc: Option, + &PruningInfo { + last_usage, + owner, + index, + forced, + }| { // Compute the age of the current operation. let age = now .checked_duration_since(last_usage) @@ -664,12 +694,20 @@ impl OperationDb { if owner == caller { if let Some(CandidateInfo { age: a, .. }) = oldest_caller_op { if age > a { - oldest_caller_op = - Some(CandidateInfo { index, malus: 0, last_usage, age }); + oldest_caller_op = Some(CandidateInfo { + index, + malus: 0, + last_usage, + age, + }); } } else { - oldest_caller_op = - Some(CandidateInfo { index, malus: 0, last_usage, age }); + oldest_caller_op = Some(CandidateInfo { + index, + malus: 0, + last_usage, + age, + }); } } @@ -692,18 +730,38 @@ impl OperationDb { // First we have to find any operation that is prunable by the caller. None => { if caller_malus < malus { - Some(CandidateInfo { index, malus, last_usage, age }) + Some(CandidateInfo { + index, + malus, + last_usage, + age, + }) } else { None } } // If we have found one we look for the operation with the worst score. // If there is a tie, the older operation is considered weaker. - Some(CandidateInfo { index: i, malus: m, last_usage: l, age: a }) => { + Some(CandidateInfo { + index: i, + malus: m, + last_usage: l, + age: a, + }) => { if malus > m || (malus == m && age > a) { - Some(CandidateInfo { index, malus, last_usage, age }) + Some(CandidateInfo { + index, + malus, + last_usage, + age, + }) } else { - Some(CandidateInfo { index: i, malus: m, last_usage: l, age: a }) + Some(CandidateInfo { + index: i, + malus: m, + last_usage: l, + age: a, + }) } } } @@ -714,7 +772,12 @@ impl OperationDb { let candidate = candidate.or(oldest_caller_op); match candidate { - Some(CandidateInfo { index, malus: _, last_usage, age: _ }) => { + Some(CandidateInfo { + index, + malus: _, + last_usage, + age: _, + }) => { match self.get(index) { Some(op) => { match op.prune(last_usage) { @@ -773,10 +836,12 @@ impl KeystoreOperation { /// BnKeystoreOperation proxy object. It also enables /// `BinderFeatures::set_requesting_sid` on the new interface, because /// we need it for checking Keystore permissions. - pub fn new_native_binder(operation: Arc) -> rsbinder::Strong { - BnKeystoreOperation::new_binder( - Self { operation: Mutex::new(Some(operation)) }, - ) + pub fn new_native_binder( + operation: Arc, + ) -> rsbinder::Strong { + BnKeystoreOperation::new_binder(Self { + operation: Mutex::new(Some(operation)), + }) } /// Grabs the outer operation mutex and calls `f` on the locked operation. @@ -824,7 +889,10 @@ impl IKeystoreOperation for KeystoreOperation { fn updateAad(&self, aad_input: &[u8]) -> Result<(), Status> { let _wp = wd::watch("IKeystoreOperation::updateAad"); self.with_locked_operation( - |op| op.update_aad(aad_input).context(err!("KeystoreOperation::updateAad")), + |op| { + op.update_aad(aad_input) + .context(err!("KeystoreOperation::updateAad")) + }, false, ) .map_err(into_logged_binder) @@ -845,7 +913,10 @@ impl IKeystoreOperation for KeystoreOperation { ) -> Result>, Status> { let _wp = wd::watch("IKeystoreOperation::finish"); self.with_locked_operation( - |op| op.finish(input, signature).context(err!("KeystoreOperation::finish")), + |op| { + op.finish(input, signature) + .context(err!("KeystoreOperation::finish")) + }, true, ) .map_err(into_logged_binder) @@ -854,7 +925,10 @@ impl IKeystoreOperation for KeystoreOperation { fn abort(&self) -> Result<(), Status> { let _wp = wd::watch("IKeystoreOperation::abort"); let result = self.with_locked_operation( - |op| op.abort(Outcome::Abort).context(err!("KeystoreOperation::abort")), + |op| { + op.abort(Outcome::Abort) + .context(err!("KeystoreOperation::abort")) + }, true, ); result.map_err(|e| { diff --git a/libs/rust/src/keymaster/security_level.rs b/libs/rust/src/keymaster/security_level.rs index 28c65d9..dc9b0cf 100644 --- a/libs/rust/src/keymaster/security_level.rs +++ b/libs/rust/src/keymaster/security_level.rs @@ -9,15 +9,28 @@ use crate::{ KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, }, system::keystore2::{ - AuthenticatorSpec::AuthenticatorSpec, CreateOperationResponse::CreateOperationResponse, Domain::Domain, EphemeralStorageKeyResponse::EphemeralStorageKeyResponse, IKeystoreOperation::IKeystoreOperation, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode + AuthenticatorSpec::AuthenticatorSpec, CreateOperationResponse::CreateOperationResponse, + Domain::Domain, EphemeralStorageKeyResponse::EphemeralStorageKeyResponse, + IKeystoreOperation::IKeystoreOperation, IKeystoreSecurityLevel::IKeystoreSecurityLevel, + KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, + ResponseCode::ResponseCode, }, }, err, global::{DB, ENFORCEMENTS, SUPER_KEY, UNDEFINED_NOT_AFTER}, keymaster::{ - attestation_key_utils::{AttestationKeyInfo, get_attest_key_info}, db::{ - BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, Uuid - }, error::{KsError, into_logged_binder, map_binder_status}, keymint_device::get_keymint_wrapper, metrics_store::log_key_creation_event_stats, operation::{KeystoreOperation, LoggingInfo, OperationDb}, super_key::{KeyBlob, SuperKeyManager}, utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params} + attestation_key_utils::{get_attest_key_info, AttestationKeyInfo}, + db::{ + BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, + KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, + Uuid, + }, + error::{into_logged_binder, map_binder_status, KsError}, + keymint_device::get_keymint_wrapper, + metrics_store::log_key_creation_event_stats, + operation::{KeystoreOperation, LoggingInfo, OperationDb}, + super_key::{KeyBlob, SuperKeyManager}, + utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, }, plat::utils::multiuser_get_user_id, }; @@ -30,7 +43,7 @@ use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; use kmr_ta::HardwareInfo; use log::debug; -use rsbinder::{Interface, Status, thread_state::CallingContext}; +use rsbinder::{thread_state::CallingContext, Interface, Status}; // Blob of 32 zeroes used as empty masking key. static ZERO_BLOB_32: &[u8] = &[0; 32]; @@ -744,24 +757,26 @@ impl<'a> KeystoreSecurityLevel<'a> { .get_after_first_unlock_key_by_user_id(multiuser_get_user_id(caller_uid)); let (key_id_guard, mut key_entry) = DB .with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| { - db.borrow_mut().load_key_entry( - key, - KeyType::Client, - KeyEntryLoadBits::KM, - caller_uid, - // |k, av| { - // check_key_permission(KeyPerm::Use, k, &av)?; - // if forced { - // check_key_permission(KeyPerm::ReqForcedOp, k, &av)?; - // } - // Ok(()) - // }, - ) + db.borrow_mut().load_key_entry( + key, + KeyType::Client, + KeyEntryLoadBits::KM, + caller_uid, + // |k, av| { + // check_key_permission(KeyPerm::Use, k, &av)?; + // if forced { + // check_key_permission(KeyPerm::ReqForcedOp, k, &av)?; + // } + // Ok(()) + // }, + ) }) .context(err!("Failed to load key blob."))?; - let (blob, blob_metadata) = - key_entry.take_key_blob_info().ok_or_else(KsError::sys).context(err!( + let (blob, blob_metadata) = key_entry + .take_key_blob_info() + .ok_or_else(KsError::sys) + .context(err!( "Successfully loaded key entry, \ but KM blob was missing." ))?; @@ -776,20 +791,26 @@ impl<'a> KeystoreSecurityLevel<'a> { } }; - let purpose = operation_parameters.iter().find(|p| p.tag == Tag::PURPOSE).map_or( - Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) - .context(err!("No operation purpose specified.")), - |kp| match kp.value { - KeyParameterValue::KeyPurpose(p) => Ok(p), - _ => Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) - .context(err!("Malformed KeyParameter.")), - }, - )?; + let purpose = operation_parameters + .iter() + .find(|p| p.tag == Tag::PURPOSE) + .map_or( + Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("No operation purpose specified.")), + |kp| match kp.value { + KeyParameterValue::KeyPurpose(p) => Ok(p), + _ => Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) + .context(err!("Malformed KeyParameter.")), + }, + )?; // Remove Tag::PURPOSE from the operation_parameters, since some keymaster devices return // an error on begin() if Tag::PURPOSE is in the operation_parameters. - let op_params: Vec = - operation_parameters.iter().filter(|p| p.tag != Tag::PURPOSE).cloned().collect(); + let op_params: Vec = operation_parameters + .iter() + .filter(|p| p.tag != Tag::PURPOSE) + .cloned() + .collect(); let operation_parameters = op_params.as_slice(); let (immediate_hat, mut auth_info) = ENFORCEMENTS @@ -857,7 +878,12 @@ impl<'a> KeystoreSecurityLevel<'a> { caller_uid, auth_info, forced, - LoggingInfo::new(self.security_level, purpose, op_params, upgraded_blob.is_some()), + LoggingInfo::new( + self.security_level, + purpose, + op_params, + upgraded_blob.is_some(), + ), ), None => { return Err(KsError::sys()).context(err!( @@ -878,12 +904,18 @@ impl<'a> KeystoreSecurityLevel<'a> { operationChallenge: operation_challenge, parameters: match begin_result.params.len() { 0 => None, - _ => Some(KeyParameters { keyParameter: begin_result.params }), + _ => Some(KeyParameters { + keyParameter: begin_result.params, + }), }, // An upgraded blob should only be returned if the caller has permission // to use Domain::BLOB keys. If we got to this point, we already checked // that the caller had that permission. - upgradedBlob: if key.domain == Domain::BLOB { upgraded_blob } else { None }, + upgradedBlob: if key.domain == Domain::BLOB { + upgraded_blob + } else { + None + }, }) } @@ -921,7 +953,8 @@ impl<'a> IKeystoreSecurityLevel for KeystoreSecurityLevel<'a> { forced: bool, ) -> Result { let _wp = self.watch("IKeystoreSecurityLevel::createOperation"); - Ok(self.create_operation(key, operation_parameters, forced) + Ok(self + .create_operation(key, operation_parameters, forced) .map_err(into_logged_binder)?) } @@ -938,7 +971,11 @@ impl<'a> IKeystoreSecurityLevel for KeystoreSecurityLevel<'a> { let _wp = self.watch_millis("IKeystoreSecurityLevel::generateKey", 5000); let result = self.generate_key(key, attestation_key, params, flags, entropy); log_key_creation_event_stats(self.security_level, params, &result); - debug!("generateKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + debug!( + "generateKey: calling uid: {}, result: {:?}", + CallingContext::default().uid, + result + ); result.map_err(into_logged_binder) } @@ -953,7 +990,11 @@ impl<'a> IKeystoreSecurityLevel for KeystoreSecurityLevel<'a> { let _wp = self.watch("IKeystoreSecurityLevel::importKey"); let result = self.import_key(key, attestation_key, params, flags, key_data); log_key_creation_event_stats(self.security_level, params, &result); - debug!("importKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + debug!( + "importKey: calling uid: {}, result: {:?}", + CallingContext::default().uid, + result + ); result.map_err(into_logged_binder) } fn importWrappedKey( @@ -968,7 +1009,11 @@ impl<'a> IKeystoreSecurityLevel for KeystoreSecurityLevel<'a> { let result = self.import_wrapped_key(key, wrapping_key, masking_key, params, authenticators); log_key_creation_event_stats(self.security_level, params, &result); - debug!("importWrappedKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + debug!( + "importWrappedKey: calling uid: {}, result: {:?}", + CallingContext::default().uid, + result + ); result.map_err(into_logged_binder) } fn convertStorageKeyToEphemeral( @@ -982,7 +1027,11 @@ impl<'a> IKeystoreSecurityLevel for KeystoreSecurityLevel<'a> { fn deleteKey(&self, key: &KeyDescriptor) -> Result<(), Status> { let _wp = self.watch("IKeystoreSecurityLevel::deleteKey"); let result = self.delete_key(key); - debug!("deleteKey: calling uid: {}, result: {:?}", CallingContext::default().uid, result); + debug!( + "deleteKey: calling uid: {}, result: {:?}", + CallingContext::default().uid, + result + ); result.map_err(into_logged_binder) } } diff --git a/libs/rust/src/logging.rs b/libs/rust/src/logging.rs index 7f4eb44..7c18210 100644 --- a/libs/rust/src/logging.rs +++ b/libs/rust/src/logging.rs @@ -18,7 +18,6 @@ pub fn init_logger() { log4rs::init_config(config).unwrap(); } - #[cfg(target_os = "android")] pub fn init_logger() { android_logger::init_once( diff --git a/libs/rust/src/plat/property_watcher.rs b/libs/rust/src/plat/property_watcher.rs index da1c2f8..657778a 100644 --- a/libs/rust/src/plat/property_watcher.rs +++ b/libs/rust/src/plat/property_watcher.rs @@ -26,7 +26,9 @@ impl PropertyWatcher { #[cfg(target_os = "android")] pub fn new(name: &str) -> anyhow::Result { - Ok(PropertyWatcher { name: name.to_string() }) + Ok(PropertyWatcher { + name: name.to_string(), + }) } pub fn read(&self) -> Result { From aabbfd8ece369df17a160468f09392aeee7d439f Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 11 Oct 2025 19:15:34 +0800 Subject: [PATCH 10/46] drop gradle Signed-off-by: qwq233 --- .gitignore | 37 +- libs/rust/Cargo.toml => Cargo.toml | 0 .../android/content/pm/IPackageManager.aidl | 0 .../hardware/security/keymint/Algorithm.aidl | 0 .../security/keymint/AttestationKey.aidl | 0 .../security/keymint/BeginResult.aidl | 0 .../hardware/security/keymint/BlockMode.aidl | 0 .../security/keymint/Certificate.aidl | 0 .../hardware/security/keymint/Digest.aidl | 0 .../hardware/security/keymint/EcCurve.aidl | 0 .../hardware/security/keymint/ErrorCode.aidl | 0 .../security/keymint/HardwareAuthToken.aidl | 0 .../keymint/HardwareAuthenticatorType.aidl | 0 .../security/keymint/IKeyMintDevice.aidl | 0 .../security/keymint/IKeyMintOperation.aidl | 0 .../security/keymint/KeyCharacteristics.aidl | 0 .../security/keymint/KeyCreationResult.aidl | 0 .../hardware/security/keymint/KeyFormat.aidl | 0 .../security/keymint/KeyMintHardwareInfo.aidl | 0 .../hardware/security/keymint/KeyOrigin.aidl | 0 .../security/keymint/KeyParameter.aidl | 0 .../security/keymint/KeyParameterValue.aidl | 0 .../hardware/security/keymint/KeyPurpose.aidl | 0 .../security/keymint/PaddingMode.aidl | 0 .../security/keymint/SecurityLevel.aidl | 0 .../hardware/security/keymint/Tag.aidl | 0 .../hardware/security/keymint/TagType.aidl | 0 .../security/secureclock/ISecureClock.aidl | 0 .../security/secureclock/TimeStampToken.aidl | 0 .../security/secureclock/Timestamp.aidl | 0 .../security/authorization/ResponseCode.aidl | 0 .../android/security/metrics/Algorithm.aidl | 0 .../android/security/metrics/AtomID.aidl | 0 .../android/security/metrics/CrashStats.aidl | 0 .../android/security/metrics/EcCurve.aidl | 0 .../metrics/HardwareAuthenticatorType.aidl | 0 .../security/metrics/IKeystoreMetrics.aidl | 0 .../metrics/KeyCreationWithAuthInfo.aidl | 0 .../metrics/KeyCreationWithGeneralInfo.aidl | 0 .../KeyCreationWithPurposeAndModesInfo.aidl | 0 .../metrics/KeyOperationWithGeneralInfo.aidl | 0 .../KeyOperationWithPurposeAndModesInfo.aidl | 0 .../android/security/metrics/KeyOrigin.aidl | 0 .../metrics/Keystore2AtomWithOverflow.aidl | 0 .../security/metrics/KeystoreAtom.aidl | 0 .../security/metrics/KeystoreAtomPayload.aidl | 0 .../android/security/metrics/Outcome.aidl | 0 .../android/security/metrics/Purpose.aidl | 0 .../android/security/metrics/RkpError.aidl | 0 .../security/metrics/RkpErrorStats.aidl | 0 .../security/metrics/SecurityLevel.aidl | 0 .../android/security/metrics/Storage.aidl | 0 .../security/metrics/StorageStats.aidl | 0 .../system/keystore2/AuthenticatorSpec.aidl | 0 .../system/keystore2/Authorization.aidl | 0 .../keystore2/CreateOperationResponse.aidl | 0 .../android/system/keystore2/Domain.aidl | 0 .../EphemeralStorageKeyResponse.aidl | 0 .../system/keystore2/IKeystoreOperation.aidl | 0 .../keystore2/IKeystoreSecurityLevel.aidl | 0 .../system/keystore2/IKeystoreService.aidl | 0 .../system/keystore2/KeyDescriptor.aidl | 0 .../system/keystore2/KeyEntryResponse.aidl | 0 .../android/system/keystore2/KeyMetadata.aidl | 0 .../system/keystore2/KeyParameters.aidl | 0 .../system/keystore2/KeyPermission.aidl | 0 .../system/keystore2/OperationChallenge.aidl | 0 .../system/keystore2/ResponseCode.aidl | 0 {libs/rust/boringssl => boringssl}/Cargo.toml | 0 {libs/rust/boringssl => boringssl}/src/aes.rs | 0 .../boringssl => boringssl}/src/aes_cmac.rs | 0 {libs/rust/boringssl => boringssl}/src/des.rs | 0 {libs/rust/boringssl => boringssl}/src/ec.rs | 0 {libs/rust/boringssl => boringssl}/src/eq.rs | 0 {libs/rust/boringssl => boringssl}/src/err.rs | 0 .../rust/boringssl => boringssl}/src/error.rs | 0 .../rust/boringssl => boringssl}/src/hmac.rs | 0 {libs/rust/boringssl => boringssl}/src/km.rs | 0 {libs/rust/boringssl => boringssl}/src/lib.rs | 0 {libs/rust/boringssl => boringssl}/src/rng.rs | 0 {libs/rust/boringssl => boringssl}/src/rsa.rs | 0 .../boringssl => boringssl}/src/sha256.rs | 0 .../rust/boringssl => boringssl}/src/tests.rs | 0 .../rust/boringssl => boringssl}/src/types.rs | 0 .../rust/boringssl => boringssl}/src/zvec.rs | 0 build.gradle.kts | 85 ----- libs/rust/build.rs => build.rs | 0 {libs/rust/common => common}/Cargo.toml | 0 {libs/rust/common => common}/fuzz/.gitignore | 0 {libs/rust/common => common}/fuzz/Cargo.toml | 0 .../fuzz/fuzz_targets/keyblob.rs | 0 {libs/rust/common => common}/generated.cddl | 0 .../common => common}/src/bin/cddl-dump.rs | 0 .../src/bin/keyblob-cddl-dump.rs | 0 {libs/rust/common => common}/src/crypto.rs | 0 .../rust/common => common}/src/crypto/aes.rs | 0 .../rust/common => common}/src/crypto/des.rs | 0 {libs/rust/common => common}/src/crypto/ec.rs | 0 .../rust/common => common}/src/crypto/hmac.rs | 0 .../rust/common => common}/src/crypto/rsa.rs | 0 .../common => common}/src/crypto/traits.rs | 0 {libs/rust/common => common}/src/keyblob.rs | 0 .../src/keyblob/keyblob.cddl | 0 .../common => common}/src/keyblob/legacy.rs | 0 .../src/keyblob/legacy/tests.rs | 0 .../common => common}/src/keyblob/sdd_mem.rs | 0 .../common => common}/src/keyblob/tests.rs | 0 {libs/rust/common => common}/src/lib.rs | 0 {libs/rust/common => common}/src/tag.rs | 0 {libs/rust/common => common}/src/tag/info.rs | 0 .../common => common}/src/tag/info/tests.rs | 0 .../rust/common => common}/src/tag/legacy.rs | 0 {libs/rust/common => common}/src/tag/tests.rs | 0 {libs/rust/derive => derive}/Cargo.toml | 0 {libs/rust/derive => derive}/src/lib.rs | 0 .../tests/integration_test.rs | 0 libs/rust/device.prop => device.prop | 0 gradle/libs.versions.toml | 20 - gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 7 - {libs/rust/hal => hal}/src/env.rs | 0 {libs/rust/hal => hal}/src/hal.rs | 0 {libs/rust/hal => hal}/src/hal/tests.rs | 0 {libs/rust/hal => hal}/src/keymint.rs | 0 {libs/rust/hal => hal}/src/lib.rs | 0 {libs/rust/hal => hal}/src/rpc.rs | 0 {libs/rust/hal => hal}/src/secureclock.rs | 0 {libs/rust/hal => hal}/src/sharedsecret.rs | 0 {libs/rust/hal => hal}/src/tests.rs | 0 libs/rust/.gitignore | 27 -- libs/rust/src/proto/mod.rs | 1 - {libs/rust/proto => proto}/storage.proto | 0 .../Cargo.toml | 0 .../README | 0 .../README.md | 0 .../src/lib.rs | 0 .../src/properties_service.rs | 0 .../src/socket_service.rs | 0 .../tests/common.rs | 0 .../tests/comprehensive_api_tests.rs | 0 .../tests/error_handling_tests.rs | 0 .../tests/example_usage_tests.rs | 0 .../tests/final_comprehensive_tests.rs | 0 .../tests/integration_tests.rs | 0 .../tests/new_api_showcase.rs | 0 .../tests/parsed_api_tests.rs | 0 .../tests/performance_tests.rs | 0 .../tests/set_api_tests.rs | 0 {libs/rust/scripts => scripts}/cddl-gen | 0 settings.gradle.kts | 35 -- {libs/rust/src => src}/.gitignore | 0 {libs/rust/src => src}/consts.rs | 0 {libs/rust/src => src}/global.rs | 0 .../keymaster/attestation_key_utils.rs | 0 {libs/rust/src => src}/keymaster/boot_key.rs | 0 {libs/rust/src => src}/keymaster/crypto.rs | 0 .../src => src}/keymaster/database/mod.rs | 0 .../src => src}/keymaster/database/perboot.rs | 0 .../src => src}/keymaster/database/utils.rs | 2 +- {libs/rust/src => src}/keymaster/db.rs | 13 - .../src => src}/keymaster/enforcements.rs | 0 {libs/rust/src => src}/keymaster/error.rs | 2 +- .../src => src}/keymaster/key_parameter.rs | 0 .../src => src}/keymaster/keymint_device.rs | 282 +++++++++++--- .../keymaster/keymint_operation.rs | 7 +- .../src => src}/keymaster/metrics_store.rs | 0 {libs/rust/src => src}/keymaster/mod.rs | 0 {libs/rust/src => src}/keymaster/operation.rs | 0 .../rust/src => src}/keymaster/permission.rs | 0 .../src => src}/keymaster/security_level.rs | 32 +- {libs/rust/src => src}/keymaster/super_key.rs | 0 {libs/rust/src => src}/keymaster/utils.rs | 361 +++++++++++++++++- {libs/rust/src => src}/keymint/attest.rs | 0 {libs/rust/src => src}/keymint/clock.rs | 0 {libs/rust/src => src}/keymint/mod.rs | 0 {libs/rust/src => src}/keymint/rpc.rs | 0 {libs/rust/src => src}/keymint/sdd.rs | 0 {libs/rust/src => src}/keymint/soft.rs | 0 {libs/rust/src => src}/lib.rs | 0 {libs/rust/src => src}/logging.rs | 0 {libs/rust/src => src}/macros.rs | 0 {libs/rust/src => src}/main.rs | 0 src/main/AndroidManifest.xml | 2 - .../java/top/qwq2333/ohmykeymint/placeholder | 1 - {libs/rust/src => src}/plat/mod.rs | 0 .../rust/src => src}/plat/property_watcher.rs | 0 {libs/rust/src => src}/plat/utils.rs | 0 {libs/rust/src => src}/utils.rs | 0 {libs/rust/src => src}/watchdog.rs | 0 {libs/rust/ta => ta}/Cargo.toml | 0 {libs/rust/ta => ta}/fuzz/.gitignore | 0 {libs/rust/ta => ta}/fuzz/Cargo.toml | 0 .../fuzz/fuzz_targets/keydescription.rs | 0 {libs/rust/ta => ta}/src/cert.rs | 0 {libs/rust/ta => ta}/src/clock.rs | 0 {libs/rust/ta => ta}/src/device.rs | 0 {libs/rust/ta => ta}/src/keys.rs | 0 {libs/rust/ta => ta}/src/lib.rs | 3 + {libs/rust/ta => ta}/src/operation.rs | 0 {libs/rust/ta => ta}/src/rkp.rs | 0 {libs/rust/ta => ta}/src/secret.rs | 0 {libs/rust/ta => ta}/src/tests.rs | 0 {libs/rust/tests => tests}/Cargo.toml | 0 .../src/bin/auth-keyblob-parse.rs | 0 .../src/bin/encrypted-keyblob-parse.rs | 0 {libs/rust/tests => tests}/src/lib.rs | 0 .../tests => tests}/tests/keyblob_test.rs | 0 {libs/rust/watchdog => watchdog}/Cargo.toml | 0 {libs/rust/watchdog => watchdog}/src/lib.rs | 0 {libs/rust/wire => wire}/Cargo.toml | 0 {libs/rust/wire => wire}/fuzz/.gitignore | 0 {libs/rust/wire => wire}/fuzz/Cargo.toml | 0 .../fuzz/fuzz_targets/legacy_message.rs | 0 .../fuzz/fuzz_targets/message.rs | 0 {libs/rust/wire => wire}/src/keymint.rs | 0 {libs/rust/wire => wire}/src/legacy.rs | 0 {libs/rust/wire => wire}/src/lib.rs | 0 {libs/rust/wire => wire}/src/rpc.rs | 0 {libs/rust/wire => wire}/src/secureclock.rs | 0 {libs/rust/wire => wire}/src/sharedsecret.rs | 0 {libs/rust/wire => wire}/src/tests.rs | 0 {libs/rust/wire => wire}/src/types.rs | 0 222 files changed, 634 insertions(+), 283 deletions(-) rename libs/rust/Cargo.toml => Cargo.toml (100%) rename {libs/rust/aidl => aidl}/android/content/pm/IPackageManager.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/Algorithm.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/AttestationKey.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/BeginResult.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/BlockMode.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/Certificate.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/Digest.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/EcCurve.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/ErrorCode.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/HardwareAuthToken.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/HardwareAuthenticatorType.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/IKeyMintDevice.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/IKeyMintOperation.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyCharacteristics.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyCreationResult.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyFormat.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyMintHardwareInfo.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyOrigin.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyParameter.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyParameterValue.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/KeyPurpose.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/PaddingMode.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/SecurityLevel.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/Tag.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/keymint/TagType.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/secureclock/ISecureClock.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/secureclock/TimeStampToken.aidl (100%) rename {libs/rust/aidl => aidl}/android/hardware/security/secureclock/Timestamp.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/authorization/ResponseCode.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/Algorithm.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/AtomID.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/CrashStats.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/EcCurve.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/HardwareAuthenticatorType.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/IKeystoreMetrics.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeyCreationWithAuthInfo.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeyCreationWithGeneralInfo.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeyOperationWithGeneralInfo.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeyOrigin.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/Keystore2AtomWithOverflow.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeystoreAtom.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/KeystoreAtomPayload.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/Outcome.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/Purpose.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/RkpError.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/RkpErrorStats.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/SecurityLevel.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/Storage.aidl (100%) rename {libs/rust/aidl => aidl}/android/security/metrics/StorageStats.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/AuthenticatorSpec.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/Authorization.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/CreateOperationResponse.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/Domain.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/EphemeralStorageKeyResponse.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/IKeystoreOperation.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/IKeystoreSecurityLevel.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/IKeystoreService.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/KeyDescriptor.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/KeyEntryResponse.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/KeyMetadata.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/KeyParameters.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/KeyPermission.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/OperationChallenge.aidl (100%) rename {libs/rust/aidl => aidl}/android/system/keystore2/ResponseCode.aidl (100%) rename {libs/rust/boringssl => boringssl}/Cargo.toml (100%) rename {libs/rust/boringssl => boringssl}/src/aes.rs (100%) rename {libs/rust/boringssl => boringssl}/src/aes_cmac.rs (100%) rename {libs/rust/boringssl => boringssl}/src/des.rs (100%) rename {libs/rust/boringssl => boringssl}/src/ec.rs (100%) rename {libs/rust/boringssl => boringssl}/src/eq.rs (100%) rename {libs/rust/boringssl => boringssl}/src/err.rs (100%) rename {libs/rust/boringssl => boringssl}/src/error.rs (100%) rename {libs/rust/boringssl => boringssl}/src/hmac.rs (100%) rename {libs/rust/boringssl => boringssl}/src/km.rs (100%) rename {libs/rust/boringssl => boringssl}/src/lib.rs (100%) rename {libs/rust/boringssl => boringssl}/src/rng.rs (100%) rename {libs/rust/boringssl => boringssl}/src/rsa.rs (100%) rename {libs/rust/boringssl => boringssl}/src/sha256.rs (100%) rename {libs/rust/boringssl => boringssl}/src/tests.rs (100%) rename {libs/rust/boringssl => boringssl}/src/types.rs (100%) rename {libs/rust/boringssl => boringssl}/src/zvec.rs (100%) delete mode 100644 build.gradle.kts rename libs/rust/build.rs => build.rs (100%) rename {libs/rust/common => common}/Cargo.toml (100%) rename {libs/rust/common => common}/fuzz/.gitignore (100%) rename {libs/rust/common => common}/fuzz/Cargo.toml (100%) rename {libs/rust/common => common}/fuzz/fuzz_targets/keyblob.rs (100%) rename {libs/rust/common => common}/generated.cddl (100%) rename {libs/rust/common => common}/src/bin/cddl-dump.rs (100%) rename {libs/rust/common => common}/src/bin/keyblob-cddl-dump.rs (100%) rename {libs/rust/common => common}/src/crypto.rs (100%) rename {libs/rust/common => common}/src/crypto/aes.rs (100%) rename {libs/rust/common => common}/src/crypto/des.rs (100%) rename {libs/rust/common => common}/src/crypto/ec.rs (100%) rename {libs/rust/common => common}/src/crypto/hmac.rs (100%) rename {libs/rust/common => common}/src/crypto/rsa.rs (100%) rename {libs/rust/common => common}/src/crypto/traits.rs (100%) rename {libs/rust/common => common}/src/keyblob.rs (100%) rename {libs/rust/common => common}/src/keyblob/keyblob.cddl (100%) rename {libs/rust/common => common}/src/keyblob/legacy.rs (100%) rename {libs/rust/common => common}/src/keyblob/legacy/tests.rs (100%) rename {libs/rust/common => common}/src/keyblob/sdd_mem.rs (100%) rename {libs/rust/common => common}/src/keyblob/tests.rs (100%) rename {libs/rust/common => common}/src/lib.rs (100%) rename {libs/rust/common => common}/src/tag.rs (100%) rename {libs/rust/common => common}/src/tag/info.rs (100%) rename {libs/rust/common => common}/src/tag/info/tests.rs (100%) rename {libs/rust/common => common}/src/tag/legacy.rs (100%) rename {libs/rust/common => common}/src/tag/tests.rs (100%) rename {libs/rust/derive => derive}/Cargo.toml (100%) rename {libs/rust/derive => derive}/src/lib.rs (100%) rename {libs/rust/derive => derive}/tests/integration_test.rs (100%) rename libs/rust/device.prop => device.prop (100%) delete mode 100644 gradle/libs.versions.toml delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties rename {libs/rust/hal => hal}/src/env.rs (100%) rename {libs/rust/hal => hal}/src/hal.rs (100%) rename {libs/rust/hal => hal}/src/hal/tests.rs (100%) rename {libs/rust/hal => hal}/src/keymint.rs (100%) rename {libs/rust/hal => hal}/src/lib.rs (100%) rename {libs/rust/hal => hal}/src/rpc.rs (100%) rename {libs/rust/hal => hal}/src/secureclock.rs (100%) rename {libs/rust/hal => hal}/src/sharedsecret.rs (100%) rename {libs/rust/hal => hal}/src/tests.rs (100%) delete mode 100644 libs/rust/.gitignore delete mode 100644 libs/rust/src/proto/mod.rs rename {libs/rust/proto => proto}/storage.proto (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/Cargo.toml (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/README (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/README.md (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/src/lib.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/src/properties_service.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/src/socket_service.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/common.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/comprehensive_api_tests.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/error_handling_tests.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/example_usage_tests.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/final_comprehensive_tests.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/integration_tests.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/new_api_showcase.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/parsed_api_tests.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/performance_tests.rs (100%) rename {libs/rust/rsproperties-service => rsproperties-service}/tests/set_api_tests.rs (100%) rename {libs/rust/scripts => scripts}/cddl-gen (100%) delete mode 100644 settings.gradle.kts rename {libs/rust/src => src}/.gitignore (100%) rename {libs/rust/src => src}/consts.rs (100%) rename {libs/rust/src => src}/global.rs (100%) rename {libs/rust/src => src}/keymaster/attestation_key_utils.rs (100%) rename {libs/rust/src => src}/keymaster/boot_key.rs (100%) rename {libs/rust/src => src}/keymaster/crypto.rs (100%) rename {libs/rust/src => src}/keymaster/database/mod.rs (100%) rename {libs/rust/src => src}/keymaster/database/perboot.rs (100%) rename {libs/rust/src => src}/keymaster/database/utils.rs (99%) rename {libs/rust/src => src}/keymaster/db.rs (99%) rename {libs/rust/src => src}/keymaster/enforcements.rs (100%) rename {libs/rust/src => src}/keymaster/error.rs (98%) rename {libs/rust/src => src}/keymaster/key_parameter.rs (100%) rename {libs/rust/src => src}/keymaster/keymint_device.rs (77%) rename {libs/rust/src => src}/keymaster/keymint_operation.rs (95%) rename {libs/rust/src => src}/keymaster/metrics_store.rs (100%) rename {libs/rust/src => src}/keymaster/mod.rs (100%) rename {libs/rust/src => src}/keymaster/operation.rs (100%) rename {libs/rust/src => src}/keymaster/permission.rs (100%) rename {libs/rust/src => src}/keymaster/security_level.rs (97%) rename {libs/rust/src => src}/keymaster/super_key.rs (100%) rename {libs/rust/src => src}/keymaster/utils.rs (58%) rename {libs/rust/src => src}/keymint/attest.rs (100%) rename {libs/rust/src => src}/keymint/clock.rs (100%) rename {libs/rust/src => src}/keymint/mod.rs (100%) rename {libs/rust/src => src}/keymint/rpc.rs (100%) rename {libs/rust/src => src}/keymint/sdd.rs (100%) rename {libs/rust/src => src}/keymint/soft.rs (100%) rename {libs/rust/src => src}/lib.rs (100%) rename {libs/rust/src => src}/logging.rs (100%) rename {libs/rust/src => src}/macros.rs (100%) rename {libs/rust/src => src}/main.rs (100%) delete mode 100644 src/main/AndroidManifest.xml delete mode 100644 src/main/java/top/qwq2333/ohmykeymint/placeholder rename {libs/rust/src => src}/plat/mod.rs (100%) rename {libs/rust/src => src}/plat/property_watcher.rs (100%) rename {libs/rust/src => src}/plat/utils.rs (100%) rename {libs/rust/src => src}/utils.rs (100%) rename {libs/rust/src => src}/watchdog.rs (100%) rename {libs/rust/ta => ta}/Cargo.toml (100%) rename {libs/rust/ta => ta}/fuzz/.gitignore (100%) rename {libs/rust/ta => ta}/fuzz/Cargo.toml (100%) rename {libs/rust/ta => ta}/fuzz/fuzz_targets/keydescription.rs (100%) rename {libs/rust/ta => ta}/src/cert.rs (100%) rename {libs/rust/ta => ta}/src/clock.rs (100%) rename {libs/rust/ta => ta}/src/device.rs (100%) rename {libs/rust/ta => ta}/src/keys.rs (100%) rename {libs/rust/ta => ta}/src/lib.rs (99%) rename {libs/rust/ta => ta}/src/operation.rs (100%) rename {libs/rust/ta => ta}/src/rkp.rs (100%) rename {libs/rust/ta => ta}/src/secret.rs (100%) rename {libs/rust/ta => ta}/src/tests.rs (100%) rename {libs/rust/tests => tests}/Cargo.toml (100%) rename {libs/rust/tests => tests}/src/bin/auth-keyblob-parse.rs (100%) rename {libs/rust/tests => tests}/src/bin/encrypted-keyblob-parse.rs (100%) rename {libs/rust/tests => tests}/src/lib.rs (100%) rename {libs/rust/tests => tests}/tests/keyblob_test.rs (100%) rename {libs/rust/watchdog => watchdog}/Cargo.toml (100%) rename {libs/rust/watchdog => watchdog}/src/lib.rs (100%) rename {libs/rust/wire => wire}/Cargo.toml (100%) rename {libs/rust/wire => wire}/fuzz/.gitignore (100%) rename {libs/rust/wire => wire}/fuzz/Cargo.toml (100%) rename {libs/rust/wire => wire}/fuzz/fuzz_targets/legacy_message.rs (100%) rename {libs/rust/wire => wire}/fuzz/fuzz_targets/message.rs (100%) rename {libs/rust/wire => wire}/src/keymint.rs (100%) rename {libs/rust/wire => wire}/src/legacy.rs (100%) rename {libs/rust/wire => wire}/src/lib.rs (100%) rename {libs/rust/wire => wire}/src/rpc.rs (100%) rename {libs/rust/wire => wire}/src/secureclock.rs (100%) rename {libs/rust/wire => wire}/src/sharedsecret.rs (100%) rename {libs/rust/wire => wire}/src/tests.rs (100%) rename {libs/rust/wire => wire}/src/types.rs (100%) diff --git a/.gitignore b/.gitignore index 2a95120..32e25f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,27 @@ -*.iml -.gradle -/local.properties -/.idea -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties +target.apk +target +.vscode + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ +build/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +/omk + +# development files +hex.txt +parcel.bin + +src/aidl.rs \ No newline at end of file diff --git a/libs/rust/Cargo.toml b/Cargo.toml similarity index 100% rename from libs/rust/Cargo.toml rename to Cargo.toml diff --git a/libs/rust/aidl/android/content/pm/IPackageManager.aidl b/aidl/android/content/pm/IPackageManager.aidl similarity index 100% rename from libs/rust/aidl/android/content/pm/IPackageManager.aidl rename to aidl/android/content/pm/IPackageManager.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/Algorithm.aidl b/aidl/android/hardware/security/keymint/Algorithm.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/Algorithm.aidl rename to aidl/android/hardware/security/keymint/Algorithm.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/AttestationKey.aidl b/aidl/android/hardware/security/keymint/AttestationKey.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/AttestationKey.aidl rename to aidl/android/hardware/security/keymint/AttestationKey.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/BeginResult.aidl b/aidl/android/hardware/security/keymint/BeginResult.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/BeginResult.aidl rename to aidl/android/hardware/security/keymint/BeginResult.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/BlockMode.aidl b/aidl/android/hardware/security/keymint/BlockMode.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/BlockMode.aidl rename to aidl/android/hardware/security/keymint/BlockMode.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/Certificate.aidl b/aidl/android/hardware/security/keymint/Certificate.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/Certificate.aidl rename to aidl/android/hardware/security/keymint/Certificate.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/Digest.aidl b/aidl/android/hardware/security/keymint/Digest.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/Digest.aidl rename to aidl/android/hardware/security/keymint/Digest.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/EcCurve.aidl b/aidl/android/hardware/security/keymint/EcCurve.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/EcCurve.aidl rename to aidl/android/hardware/security/keymint/EcCurve.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/ErrorCode.aidl b/aidl/android/hardware/security/keymint/ErrorCode.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/ErrorCode.aidl rename to aidl/android/hardware/security/keymint/ErrorCode.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl b/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/HardwareAuthToken.aidl rename to aidl/android/hardware/security/keymint/HardwareAuthToken.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl b/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl rename to aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl rename to aidl/android/hardware/security/keymint/IKeyMintDevice.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl b/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl rename to aidl/android/hardware/security/keymint/IKeyMintOperation.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl b/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyCharacteristics.aidl rename to aidl/android/hardware/security/keymint/KeyCharacteristics.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyCreationResult.aidl b/aidl/android/hardware/security/keymint/KeyCreationResult.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyCreationResult.aidl rename to aidl/android/hardware/security/keymint/KeyCreationResult.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyFormat.aidl b/aidl/android/hardware/security/keymint/KeyFormat.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyFormat.aidl rename to aidl/android/hardware/security/keymint/KeyFormat.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl b/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl rename to aidl/android/hardware/security/keymint/KeyMintHardwareInfo.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyOrigin.aidl b/aidl/android/hardware/security/keymint/KeyOrigin.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyOrigin.aidl rename to aidl/android/hardware/security/keymint/KeyOrigin.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyParameter.aidl b/aidl/android/hardware/security/keymint/KeyParameter.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyParameter.aidl rename to aidl/android/hardware/security/keymint/KeyParameter.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyParameterValue.aidl b/aidl/android/hardware/security/keymint/KeyParameterValue.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyParameterValue.aidl rename to aidl/android/hardware/security/keymint/KeyParameterValue.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/KeyPurpose.aidl b/aidl/android/hardware/security/keymint/KeyPurpose.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/KeyPurpose.aidl rename to aidl/android/hardware/security/keymint/KeyPurpose.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/PaddingMode.aidl b/aidl/android/hardware/security/keymint/PaddingMode.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/PaddingMode.aidl rename to aidl/android/hardware/security/keymint/PaddingMode.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/SecurityLevel.aidl b/aidl/android/hardware/security/keymint/SecurityLevel.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/SecurityLevel.aidl rename to aidl/android/hardware/security/keymint/SecurityLevel.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/Tag.aidl b/aidl/android/hardware/security/keymint/Tag.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/Tag.aidl rename to aidl/android/hardware/security/keymint/Tag.aidl diff --git a/libs/rust/aidl/android/hardware/security/keymint/TagType.aidl b/aidl/android/hardware/security/keymint/TagType.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/keymint/TagType.aidl rename to aidl/android/hardware/security/keymint/TagType.aidl diff --git a/libs/rust/aidl/android/hardware/security/secureclock/ISecureClock.aidl b/aidl/android/hardware/security/secureclock/ISecureClock.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/secureclock/ISecureClock.aidl rename to aidl/android/hardware/security/secureclock/ISecureClock.aidl diff --git a/libs/rust/aidl/android/hardware/security/secureclock/TimeStampToken.aidl b/aidl/android/hardware/security/secureclock/TimeStampToken.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/secureclock/TimeStampToken.aidl rename to aidl/android/hardware/security/secureclock/TimeStampToken.aidl diff --git a/libs/rust/aidl/android/hardware/security/secureclock/Timestamp.aidl b/aidl/android/hardware/security/secureclock/Timestamp.aidl similarity index 100% rename from libs/rust/aidl/android/hardware/security/secureclock/Timestamp.aidl rename to aidl/android/hardware/security/secureclock/Timestamp.aidl diff --git a/libs/rust/aidl/android/security/authorization/ResponseCode.aidl b/aidl/android/security/authorization/ResponseCode.aidl similarity index 100% rename from libs/rust/aidl/android/security/authorization/ResponseCode.aidl rename to aidl/android/security/authorization/ResponseCode.aidl diff --git a/libs/rust/aidl/android/security/metrics/Algorithm.aidl b/aidl/android/security/metrics/Algorithm.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/Algorithm.aidl rename to aidl/android/security/metrics/Algorithm.aidl diff --git a/libs/rust/aidl/android/security/metrics/AtomID.aidl b/aidl/android/security/metrics/AtomID.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/AtomID.aidl rename to aidl/android/security/metrics/AtomID.aidl diff --git a/libs/rust/aidl/android/security/metrics/CrashStats.aidl b/aidl/android/security/metrics/CrashStats.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/CrashStats.aidl rename to aidl/android/security/metrics/CrashStats.aidl diff --git a/libs/rust/aidl/android/security/metrics/EcCurve.aidl b/aidl/android/security/metrics/EcCurve.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/EcCurve.aidl rename to aidl/android/security/metrics/EcCurve.aidl diff --git a/libs/rust/aidl/android/security/metrics/HardwareAuthenticatorType.aidl b/aidl/android/security/metrics/HardwareAuthenticatorType.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/HardwareAuthenticatorType.aidl rename to aidl/android/security/metrics/HardwareAuthenticatorType.aidl diff --git a/libs/rust/aidl/android/security/metrics/IKeystoreMetrics.aidl b/aidl/android/security/metrics/IKeystoreMetrics.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/IKeystoreMetrics.aidl rename to aidl/android/security/metrics/IKeystoreMetrics.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl b/aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl rename to aidl/android/security/metrics/KeyCreationWithAuthInfo.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl b/aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl rename to aidl/android/security/metrics/KeyCreationWithGeneralInfo.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl b/aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl rename to aidl/android/security/metrics/KeyCreationWithPurposeAndModesInfo.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl b/aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl rename to aidl/android/security/metrics/KeyOperationWithGeneralInfo.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl b/aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl rename to aidl/android/security/metrics/KeyOperationWithPurposeAndModesInfo.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeyOrigin.aidl b/aidl/android/security/metrics/KeyOrigin.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeyOrigin.aidl rename to aidl/android/security/metrics/KeyOrigin.aidl diff --git a/libs/rust/aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl b/aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl rename to aidl/android/security/metrics/Keystore2AtomWithOverflow.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeystoreAtom.aidl b/aidl/android/security/metrics/KeystoreAtom.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeystoreAtom.aidl rename to aidl/android/security/metrics/KeystoreAtom.aidl diff --git a/libs/rust/aidl/android/security/metrics/KeystoreAtomPayload.aidl b/aidl/android/security/metrics/KeystoreAtomPayload.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/KeystoreAtomPayload.aidl rename to aidl/android/security/metrics/KeystoreAtomPayload.aidl diff --git a/libs/rust/aidl/android/security/metrics/Outcome.aidl b/aidl/android/security/metrics/Outcome.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/Outcome.aidl rename to aidl/android/security/metrics/Outcome.aidl diff --git a/libs/rust/aidl/android/security/metrics/Purpose.aidl b/aidl/android/security/metrics/Purpose.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/Purpose.aidl rename to aidl/android/security/metrics/Purpose.aidl diff --git a/libs/rust/aidl/android/security/metrics/RkpError.aidl b/aidl/android/security/metrics/RkpError.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/RkpError.aidl rename to aidl/android/security/metrics/RkpError.aidl diff --git a/libs/rust/aidl/android/security/metrics/RkpErrorStats.aidl b/aidl/android/security/metrics/RkpErrorStats.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/RkpErrorStats.aidl rename to aidl/android/security/metrics/RkpErrorStats.aidl diff --git a/libs/rust/aidl/android/security/metrics/SecurityLevel.aidl b/aidl/android/security/metrics/SecurityLevel.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/SecurityLevel.aidl rename to aidl/android/security/metrics/SecurityLevel.aidl diff --git a/libs/rust/aidl/android/security/metrics/Storage.aidl b/aidl/android/security/metrics/Storage.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/Storage.aidl rename to aidl/android/security/metrics/Storage.aidl diff --git a/libs/rust/aidl/android/security/metrics/StorageStats.aidl b/aidl/android/security/metrics/StorageStats.aidl similarity index 100% rename from libs/rust/aidl/android/security/metrics/StorageStats.aidl rename to aidl/android/security/metrics/StorageStats.aidl diff --git a/libs/rust/aidl/android/system/keystore2/AuthenticatorSpec.aidl b/aidl/android/system/keystore2/AuthenticatorSpec.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/AuthenticatorSpec.aidl rename to aidl/android/system/keystore2/AuthenticatorSpec.aidl diff --git a/libs/rust/aidl/android/system/keystore2/Authorization.aidl b/aidl/android/system/keystore2/Authorization.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/Authorization.aidl rename to aidl/android/system/keystore2/Authorization.aidl diff --git a/libs/rust/aidl/android/system/keystore2/CreateOperationResponse.aidl b/aidl/android/system/keystore2/CreateOperationResponse.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/CreateOperationResponse.aidl rename to aidl/android/system/keystore2/CreateOperationResponse.aidl diff --git a/libs/rust/aidl/android/system/keystore2/Domain.aidl b/aidl/android/system/keystore2/Domain.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/Domain.aidl rename to aidl/android/system/keystore2/Domain.aidl diff --git a/libs/rust/aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl b/aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl rename to aidl/android/system/keystore2/EphemeralStorageKeyResponse.aidl diff --git a/libs/rust/aidl/android/system/keystore2/IKeystoreOperation.aidl b/aidl/android/system/keystore2/IKeystoreOperation.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/IKeystoreOperation.aidl rename to aidl/android/system/keystore2/IKeystoreOperation.aidl diff --git a/libs/rust/aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl b/aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl rename to aidl/android/system/keystore2/IKeystoreSecurityLevel.aidl diff --git a/libs/rust/aidl/android/system/keystore2/IKeystoreService.aidl b/aidl/android/system/keystore2/IKeystoreService.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/IKeystoreService.aidl rename to aidl/android/system/keystore2/IKeystoreService.aidl diff --git a/libs/rust/aidl/android/system/keystore2/KeyDescriptor.aidl b/aidl/android/system/keystore2/KeyDescriptor.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/KeyDescriptor.aidl rename to aidl/android/system/keystore2/KeyDescriptor.aidl diff --git a/libs/rust/aidl/android/system/keystore2/KeyEntryResponse.aidl b/aidl/android/system/keystore2/KeyEntryResponse.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/KeyEntryResponse.aidl rename to aidl/android/system/keystore2/KeyEntryResponse.aidl diff --git a/libs/rust/aidl/android/system/keystore2/KeyMetadata.aidl b/aidl/android/system/keystore2/KeyMetadata.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/KeyMetadata.aidl rename to aidl/android/system/keystore2/KeyMetadata.aidl diff --git a/libs/rust/aidl/android/system/keystore2/KeyParameters.aidl b/aidl/android/system/keystore2/KeyParameters.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/KeyParameters.aidl rename to aidl/android/system/keystore2/KeyParameters.aidl diff --git a/libs/rust/aidl/android/system/keystore2/KeyPermission.aidl b/aidl/android/system/keystore2/KeyPermission.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/KeyPermission.aidl rename to aidl/android/system/keystore2/KeyPermission.aidl diff --git a/libs/rust/aidl/android/system/keystore2/OperationChallenge.aidl b/aidl/android/system/keystore2/OperationChallenge.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/OperationChallenge.aidl rename to aidl/android/system/keystore2/OperationChallenge.aidl diff --git a/libs/rust/aidl/android/system/keystore2/ResponseCode.aidl b/aidl/android/system/keystore2/ResponseCode.aidl similarity index 100% rename from libs/rust/aidl/android/system/keystore2/ResponseCode.aidl rename to aidl/android/system/keystore2/ResponseCode.aidl diff --git a/libs/rust/boringssl/Cargo.toml b/boringssl/Cargo.toml similarity index 100% rename from libs/rust/boringssl/Cargo.toml rename to boringssl/Cargo.toml diff --git a/libs/rust/boringssl/src/aes.rs b/boringssl/src/aes.rs similarity index 100% rename from libs/rust/boringssl/src/aes.rs rename to boringssl/src/aes.rs diff --git a/libs/rust/boringssl/src/aes_cmac.rs b/boringssl/src/aes_cmac.rs similarity index 100% rename from libs/rust/boringssl/src/aes_cmac.rs rename to boringssl/src/aes_cmac.rs diff --git a/libs/rust/boringssl/src/des.rs b/boringssl/src/des.rs similarity index 100% rename from libs/rust/boringssl/src/des.rs rename to boringssl/src/des.rs diff --git a/libs/rust/boringssl/src/ec.rs b/boringssl/src/ec.rs similarity index 100% rename from libs/rust/boringssl/src/ec.rs rename to boringssl/src/ec.rs diff --git a/libs/rust/boringssl/src/eq.rs b/boringssl/src/eq.rs similarity index 100% rename from libs/rust/boringssl/src/eq.rs rename to boringssl/src/eq.rs diff --git a/libs/rust/boringssl/src/err.rs b/boringssl/src/err.rs similarity index 100% rename from libs/rust/boringssl/src/err.rs rename to boringssl/src/err.rs diff --git a/libs/rust/boringssl/src/error.rs b/boringssl/src/error.rs similarity index 100% rename from libs/rust/boringssl/src/error.rs rename to boringssl/src/error.rs diff --git a/libs/rust/boringssl/src/hmac.rs b/boringssl/src/hmac.rs similarity index 100% rename from libs/rust/boringssl/src/hmac.rs rename to boringssl/src/hmac.rs diff --git a/libs/rust/boringssl/src/km.rs b/boringssl/src/km.rs similarity index 100% rename from libs/rust/boringssl/src/km.rs rename to boringssl/src/km.rs diff --git a/libs/rust/boringssl/src/lib.rs b/boringssl/src/lib.rs similarity index 100% rename from libs/rust/boringssl/src/lib.rs rename to boringssl/src/lib.rs diff --git a/libs/rust/boringssl/src/rng.rs b/boringssl/src/rng.rs similarity index 100% rename from libs/rust/boringssl/src/rng.rs rename to boringssl/src/rng.rs diff --git a/libs/rust/boringssl/src/rsa.rs b/boringssl/src/rsa.rs similarity index 100% rename from libs/rust/boringssl/src/rsa.rs rename to boringssl/src/rsa.rs diff --git a/libs/rust/boringssl/src/sha256.rs b/boringssl/src/sha256.rs similarity index 100% rename from libs/rust/boringssl/src/sha256.rs rename to boringssl/src/sha256.rs diff --git a/libs/rust/boringssl/src/tests.rs b/boringssl/src/tests.rs similarity index 100% rename from libs/rust/boringssl/src/tests.rs rename to boringssl/src/tests.rs diff --git a/libs/rust/boringssl/src/types.rs b/boringssl/src/types.rs similarity index 100% rename from libs/rust/boringssl/src/types.rs rename to boringssl/src/types.rs diff --git a/libs/rust/boringssl/src/zvec.rs b/boringssl/src/zvec.rs similarity index 100% rename from libs/rust/boringssl/src/zvec.rs rename to boringssl/src/zvec.rs diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index da9e8fb..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,85 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -import com.android.build.api.variant.BuildConfigField -import com.android.build.api.variant.FilterConfiguration -import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties -import java.text.SimpleDateFormat -import java.util.Date - -plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.serialization) - alias(libs.plugins.rust) -} - -cargo { - module = "./libs/rust" - libname = "keymint" - targets = listOf("arm64", "arm") - - prebuiltToolchains = true - profile = "release" -} - -dependencies { - implementation(libs.kotlin.stdlib.common) - implementation(libs.kotlin.stdlib) - implementation(libs.kotlinx.serialization.json) -} - -android { - defaultConfig.applicationId = "top.qwq2333.ohmykeymint" - namespace = "top.qwq2333.ohmykeymint" - - sourceSets.getByName("main") { - java.srcDir("src/main/java") - } - - lint { - checkReleaseBuilds = true - disable += listOf( - "MissingTranslation", "ExtraTranslation", "BlockedPrivateApi" - ) - } - - packaging { - resources.excludes += "**" - } - - kotlin { - jvmToolchain(Version.java.toString().toInt()) - } - - buildTypes { - getByName("release") { - signingConfig = signingConfigs.getByName("debug") - isMinifyEnabled = false - isShrinkResources = true - } - - getByName("debug") { - isDefault = true - isDebuggable = true - isJniDebuggable = true - } - } - - buildFeatures { - buildConfig = true - } - - defaultConfig { - buildConfigField("String", "BUILD_TIME", "\"${SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())}\"") - } - - applicationVariants.all { - outputs.all { - val abi = this.filters.find { it.filterType == FilterConfiguration.FilterType.ABI.name }?.identifier - val output = this as? com.android.build.gradle.internal.api.BaseVariantOutputImpl - val outputFileName = "OhMyKeymint-${defaultConfig.versionName}-${abiName[abi]}.apk" - output?.outputFileName = outputFileName - } - } -} - diff --git a/libs/rust/build.rs b/build.rs similarity index 100% rename from libs/rust/build.rs rename to build.rs diff --git a/libs/rust/common/Cargo.toml b/common/Cargo.toml similarity index 100% rename from libs/rust/common/Cargo.toml rename to common/Cargo.toml diff --git a/libs/rust/common/fuzz/.gitignore b/common/fuzz/.gitignore similarity index 100% rename from libs/rust/common/fuzz/.gitignore rename to common/fuzz/.gitignore diff --git a/libs/rust/common/fuzz/Cargo.toml b/common/fuzz/Cargo.toml similarity index 100% rename from libs/rust/common/fuzz/Cargo.toml rename to common/fuzz/Cargo.toml diff --git a/libs/rust/common/fuzz/fuzz_targets/keyblob.rs b/common/fuzz/fuzz_targets/keyblob.rs similarity index 100% rename from libs/rust/common/fuzz/fuzz_targets/keyblob.rs rename to common/fuzz/fuzz_targets/keyblob.rs diff --git a/libs/rust/common/generated.cddl b/common/generated.cddl similarity index 100% rename from libs/rust/common/generated.cddl rename to common/generated.cddl diff --git a/libs/rust/common/src/bin/cddl-dump.rs b/common/src/bin/cddl-dump.rs similarity index 100% rename from libs/rust/common/src/bin/cddl-dump.rs rename to common/src/bin/cddl-dump.rs diff --git a/libs/rust/common/src/bin/keyblob-cddl-dump.rs b/common/src/bin/keyblob-cddl-dump.rs similarity index 100% rename from libs/rust/common/src/bin/keyblob-cddl-dump.rs rename to common/src/bin/keyblob-cddl-dump.rs diff --git a/libs/rust/common/src/crypto.rs b/common/src/crypto.rs similarity index 100% rename from libs/rust/common/src/crypto.rs rename to common/src/crypto.rs diff --git a/libs/rust/common/src/crypto/aes.rs b/common/src/crypto/aes.rs similarity index 100% rename from libs/rust/common/src/crypto/aes.rs rename to common/src/crypto/aes.rs diff --git a/libs/rust/common/src/crypto/des.rs b/common/src/crypto/des.rs similarity index 100% rename from libs/rust/common/src/crypto/des.rs rename to common/src/crypto/des.rs diff --git a/libs/rust/common/src/crypto/ec.rs b/common/src/crypto/ec.rs similarity index 100% rename from libs/rust/common/src/crypto/ec.rs rename to common/src/crypto/ec.rs diff --git a/libs/rust/common/src/crypto/hmac.rs b/common/src/crypto/hmac.rs similarity index 100% rename from libs/rust/common/src/crypto/hmac.rs rename to common/src/crypto/hmac.rs diff --git a/libs/rust/common/src/crypto/rsa.rs b/common/src/crypto/rsa.rs similarity index 100% rename from libs/rust/common/src/crypto/rsa.rs rename to common/src/crypto/rsa.rs diff --git a/libs/rust/common/src/crypto/traits.rs b/common/src/crypto/traits.rs similarity index 100% rename from libs/rust/common/src/crypto/traits.rs rename to common/src/crypto/traits.rs diff --git a/libs/rust/common/src/keyblob.rs b/common/src/keyblob.rs similarity index 100% rename from libs/rust/common/src/keyblob.rs rename to common/src/keyblob.rs diff --git a/libs/rust/common/src/keyblob/keyblob.cddl b/common/src/keyblob/keyblob.cddl similarity index 100% rename from libs/rust/common/src/keyblob/keyblob.cddl rename to common/src/keyblob/keyblob.cddl diff --git a/libs/rust/common/src/keyblob/legacy.rs b/common/src/keyblob/legacy.rs similarity index 100% rename from libs/rust/common/src/keyblob/legacy.rs rename to common/src/keyblob/legacy.rs diff --git a/libs/rust/common/src/keyblob/legacy/tests.rs b/common/src/keyblob/legacy/tests.rs similarity index 100% rename from libs/rust/common/src/keyblob/legacy/tests.rs rename to common/src/keyblob/legacy/tests.rs diff --git a/libs/rust/common/src/keyblob/sdd_mem.rs b/common/src/keyblob/sdd_mem.rs similarity index 100% rename from libs/rust/common/src/keyblob/sdd_mem.rs rename to common/src/keyblob/sdd_mem.rs diff --git a/libs/rust/common/src/keyblob/tests.rs b/common/src/keyblob/tests.rs similarity index 100% rename from libs/rust/common/src/keyblob/tests.rs rename to common/src/keyblob/tests.rs diff --git a/libs/rust/common/src/lib.rs b/common/src/lib.rs similarity index 100% rename from libs/rust/common/src/lib.rs rename to common/src/lib.rs diff --git a/libs/rust/common/src/tag.rs b/common/src/tag.rs similarity index 100% rename from libs/rust/common/src/tag.rs rename to common/src/tag.rs diff --git a/libs/rust/common/src/tag/info.rs b/common/src/tag/info.rs similarity index 100% rename from libs/rust/common/src/tag/info.rs rename to common/src/tag/info.rs diff --git a/libs/rust/common/src/tag/info/tests.rs b/common/src/tag/info/tests.rs similarity index 100% rename from libs/rust/common/src/tag/info/tests.rs rename to common/src/tag/info/tests.rs diff --git a/libs/rust/common/src/tag/legacy.rs b/common/src/tag/legacy.rs similarity index 100% rename from libs/rust/common/src/tag/legacy.rs rename to common/src/tag/legacy.rs diff --git a/libs/rust/common/src/tag/tests.rs b/common/src/tag/tests.rs similarity index 100% rename from libs/rust/common/src/tag/tests.rs rename to common/src/tag/tests.rs diff --git a/libs/rust/derive/Cargo.toml b/derive/Cargo.toml similarity index 100% rename from libs/rust/derive/Cargo.toml rename to derive/Cargo.toml diff --git a/libs/rust/derive/src/lib.rs b/derive/src/lib.rs similarity index 100% rename from libs/rust/derive/src/lib.rs rename to derive/src/lib.rs diff --git a/libs/rust/derive/tests/integration_test.rs b/derive/tests/integration_test.rs similarity index 100% rename from libs/rust/derive/tests/integration_test.rs rename to derive/tests/integration_test.rs diff --git a/libs/rust/device.prop b/device.prop similarity index 100% rename from libs/rust/device.prop rename to device.prop diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index 07c365d..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,20 +0,0 @@ -[versions] -agp = "8.12.0" -kotlin = "2.2.20" -kotlinxCoroutinesAndroid = "1.10.2" -kotlinxSerializationJson = "1.9.0" -rust = "0.9.6" - -[libraries] -kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } -kotlin-stdlib-common = { module = "org.jetbrains.kotlin:kotlin-stdlib-common", version.ref = "kotlin" } -kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } - -[plugins] -android-application = { id = "com.android.application", version.ref = "agp" } -android-library = { id = "com.android.library", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -rust = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rust" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917707c1f8861d8cb53dd15194d4248596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2e11132..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/libs/rust/hal/src/env.rs b/hal/src/env.rs similarity index 100% rename from libs/rust/hal/src/env.rs rename to hal/src/env.rs diff --git a/libs/rust/hal/src/hal.rs b/hal/src/hal.rs similarity index 100% rename from libs/rust/hal/src/hal.rs rename to hal/src/hal.rs diff --git a/libs/rust/hal/src/hal/tests.rs b/hal/src/hal/tests.rs similarity index 100% rename from libs/rust/hal/src/hal/tests.rs rename to hal/src/hal/tests.rs diff --git a/libs/rust/hal/src/keymint.rs b/hal/src/keymint.rs similarity index 100% rename from libs/rust/hal/src/keymint.rs rename to hal/src/keymint.rs diff --git a/libs/rust/hal/src/lib.rs b/hal/src/lib.rs similarity index 100% rename from libs/rust/hal/src/lib.rs rename to hal/src/lib.rs diff --git a/libs/rust/hal/src/rpc.rs b/hal/src/rpc.rs similarity index 100% rename from libs/rust/hal/src/rpc.rs rename to hal/src/rpc.rs diff --git a/libs/rust/hal/src/secureclock.rs b/hal/src/secureclock.rs similarity index 100% rename from libs/rust/hal/src/secureclock.rs rename to hal/src/secureclock.rs diff --git a/libs/rust/hal/src/sharedsecret.rs b/hal/src/sharedsecret.rs similarity index 100% rename from libs/rust/hal/src/sharedsecret.rs rename to hal/src/sharedsecret.rs diff --git a/libs/rust/hal/src/tests.rs b/hal/src/tests.rs similarity index 100% rename from libs/rust/hal/src/tests.rs rename to hal/src/tests.rs diff --git a/libs/rust/.gitignore b/libs/rust/.gitignore deleted file mode 100644 index 32e25f3..0000000 --- a/libs/rust/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -target.apk -target -.vscode - -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ -build/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -/omk - -# development files -hex.txt -parcel.bin - -src/aidl.rs \ No newline at end of file diff --git a/libs/rust/src/proto/mod.rs b/libs/rust/src/proto/mod.rs deleted file mode 100644 index e626ba5..0000000 --- a/libs/rust/src/proto/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod storage; \ No newline at end of file diff --git a/libs/rust/proto/storage.proto b/proto/storage.proto similarity index 100% rename from libs/rust/proto/storage.proto rename to proto/storage.proto diff --git a/libs/rust/rsproperties-service/Cargo.toml b/rsproperties-service/Cargo.toml similarity index 100% rename from libs/rust/rsproperties-service/Cargo.toml rename to rsproperties-service/Cargo.toml diff --git a/libs/rust/rsproperties-service/README b/rsproperties-service/README similarity index 100% rename from libs/rust/rsproperties-service/README rename to rsproperties-service/README diff --git a/libs/rust/rsproperties-service/README.md b/rsproperties-service/README.md similarity index 100% rename from libs/rust/rsproperties-service/README.md rename to rsproperties-service/README.md diff --git a/libs/rust/rsproperties-service/src/lib.rs b/rsproperties-service/src/lib.rs similarity index 100% rename from libs/rust/rsproperties-service/src/lib.rs rename to rsproperties-service/src/lib.rs diff --git a/libs/rust/rsproperties-service/src/properties_service.rs b/rsproperties-service/src/properties_service.rs similarity index 100% rename from libs/rust/rsproperties-service/src/properties_service.rs rename to rsproperties-service/src/properties_service.rs diff --git a/libs/rust/rsproperties-service/src/socket_service.rs b/rsproperties-service/src/socket_service.rs similarity index 100% rename from libs/rust/rsproperties-service/src/socket_service.rs rename to rsproperties-service/src/socket_service.rs diff --git a/libs/rust/rsproperties-service/tests/common.rs b/rsproperties-service/tests/common.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/common.rs rename to rsproperties-service/tests/common.rs diff --git a/libs/rust/rsproperties-service/tests/comprehensive_api_tests.rs b/rsproperties-service/tests/comprehensive_api_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/comprehensive_api_tests.rs rename to rsproperties-service/tests/comprehensive_api_tests.rs diff --git a/libs/rust/rsproperties-service/tests/error_handling_tests.rs b/rsproperties-service/tests/error_handling_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/error_handling_tests.rs rename to rsproperties-service/tests/error_handling_tests.rs diff --git a/libs/rust/rsproperties-service/tests/example_usage_tests.rs b/rsproperties-service/tests/example_usage_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/example_usage_tests.rs rename to rsproperties-service/tests/example_usage_tests.rs diff --git a/libs/rust/rsproperties-service/tests/final_comprehensive_tests.rs b/rsproperties-service/tests/final_comprehensive_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/final_comprehensive_tests.rs rename to rsproperties-service/tests/final_comprehensive_tests.rs diff --git a/libs/rust/rsproperties-service/tests/integration_tests.rs b/rsproperties-service/tests/integration_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/integration_tests.rs rename to rsproperties-service/tests/integration_tests.rs diff --git a/libs/rust/rsproperties-service/tests/new_api_showcase.rs b/rsproperties-service/tests/new_api_showcase.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/new_api_showcase.rs rename to rsproperties-service/tests/new_api_showcase.rs diff --git a/libs/rust/rsproperties-service/tests/parsed_api_tests.rs b/rsproperties-service/tests/parsed_api_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/parsed_api_tests.rs rename to rsproperties-service/tests/parsed_api_tests.rs diff --git a/libs/rust/rsproperties-service/tests/performance_tests.rs b/rsproperties-service/tests/performance_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/performance_tests.rs rename to rsproperties-service/tests/performance_tests.rs diff --git a/libs/rust/rsproperties-service/tests/set_api_tests.rs b/rsproperties-service/tests/set_api_tests.rs similarity index 100% rename from libs/rust/rsproperties-service/tests/set_api_tests.rs rename to rsproperties-service/tests/set_api_tests.rs diff --git a/libs/rust/scripts/cddl-gen b/scripts/cddl-gen similarity index 100% rename from libs/rust/scripts/cddl-gen rename to scripts/cddl-gen diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index ec6f217..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -@file:Suppress("UnstableApiUsage") -pluginManagement { - repositories { - gradlePluginPortal() - google() - mavenCentral() - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - gradlePluginPortal() - - maven("https://jitpack.io") - } -} - -plugins { - id("com.gradle.develocity") version "3.19.2" -} - -develocity { - buildScan { - publishing.onlyIf { - System.getenv("GITHUB_ACTIONS") == "true" || it.buildResult.failures.isNotEmpty() - } - termsOfUseAgree.set("yes") - termsOfUseUrl.set("https://gradle.com/terms-of-service") - } -} - -rootProject.name = "OhMyKeymint" diff --git a/libs/rust/src/.gitignore b/src/.gitignore similarity index 100% rename from libs/rust/src/.gitignore rename to src/.gitignore diff --git a/libs/rust/src/consts.rs b/src/consts.rs similarity index 100% rename from libs/rust/src/consts.rs rename to src/consts.rs diff --git a/libs/rust/src/global.rs b/src/global.rs similarity index 100% rename from libs/rust/src/global.rs rename to src/global.rs diff --git a/libs/rust/src/keymaster/attestation_key_utils.rs b/src/keymaster/attestation_key_utils.rs similarity index 100% rename from libs/rust/src/keymaster/attestation_key_utils.rs rename to src/keymaster/attestation_key_utils.rs diff --git a/libs/rust/src/keymaster/boot_key.rs b/src/keymaster/boot_key.rs similarity index 100% rename from libs/rust/src/keymaster/boot_key.rs rename to src/keymaster/boot_key.rs diff --git a/libs/rust/src/keymaster/crypto.rs b/src/keymaster/crypto.rs similarity index 100% rename from libs/rust/src/keymaster/crypto.rs rename to src/keymaster/crypto.rs diff --git a/libs/rust/src/keymaster/database/mod.rs b/src/keymaster/database/mod.rs similarity index 100% rename from libs/rust/src/keymaster/database/mod.rs rename to src/keymaster/database/mod.rs diff --git a/libs/rust/src/keymaster/database/perboot.rs b/src/keymaster/database/perboot.rs similarity index 100% rename from libs/rust/src/keymaster/database/perboot.rs rename to src/keymaster/database/perboot.rs diff --git a/libs/rust/src/keymaster/database/utils.rs b/src/keymaster/database/utils.rs similarity index 99% rename from libs/rust/src/keymaster/database/utils.rs rename to src/keymaster/database/utils.rs index f2c467b..5d81d20 100644 --- a/libs/rust/src/keymaster/database/utils.rs +++ b/src/keymaster/database/utils.rs @@ -194,7 +194,7 @@ macro_rules! impl_metadata { } impl rusqlite::types::ToSql for $entry { - fn to_sql(&self) -> rusqlite::Result { + fn to_sql(&self) -> rusqlite::Result> { match self { $($entry::$vname(v) => v.to_sql(),)* } diff --git a/libs/rust/src/keymaster/db.rs b/src/keymaster/db.rs similarity index 99% rename from libs/rust/src/keymaster/db.rs rename to src/keymaster/db.rs index 8e8953e..120dd1b 100644 --- a/libs/rust/src/keymaster/db.rs +++ b/src/keymaster/db.rs @@ -956,19 +956,6 @@ impl KeymasterDb { // database here and then immediately replaced by the superseding blob. // The garbage collector will then subject the blob to deleteKey of the // KM back end to permanently invalidate the key. - let need_gc = if let Some((blob, blob_metadata)) = superseded_blob { - Self::set_blob_internal( - tx, - key_id.id(), - SubComponentType::KEY_BLOB, - Some(blob), - Some(blob_metadata), - ) - .context("Trying to insert superseded key blob.")?; - true - } else { - false - }; Self::set_blob_internal( tx, diff --git a/libs/rust/src/keymaster/enforcements.rs b/src/keymaster/enforcements.rs similarity index 100% rename from libs/rust/src/keymaster/enforcements.rs rename to src/keymaster/enforcements.rs diff --git a/libs/rust/src/keymaster/error.rs b/src/keymaster/error.rs similarity index 98% rename from libs/rust/src/keymaster/error.rs rename to src/keymaster/error.rs index a15ea69..7666031 100644 --- a/libs/rust/src/keymaster/error.rs +++ b/src/keymaster/error.rs @@ -61,7 +61,7 @@ pub fn map_ks_error(r: KsError) -> Status { KsError::Km(ec) => { Status::new_service_specific_error(ec.0, format!("KeymintError: {:?}", ec).into()) } - KsError::Binder(ec, se) => Status::from(ec), + KsError::Binder(ec, _se) => Status::from(ec), KsError::BinderTransaction(sc) => Status::from(sc), } } diff --git a/libs/rust/src/keymaster/key_parameter.rs b/src/keymaster/key_parameter.rs similarity index 100% rename from libs/rust/src/keymaster/key_parameter.rs rename to src/keymaster/key_parameter.rs diff --git a/libs/rust/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs similarity index 77% rename from libs/rust/src/keymaster/keymint_device.rs rename to src/keymaster/keymint_device.rs index dade1d4..d2731eb 100644 --- a/libs/rust/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -29,6 +29,8 @@ use crate::android::system::keystore2::{ use crate::global::AID_KEYSTORE; use crate::keymaster::db::Uuid; use crate::keymaster::error::{map_binder_status, map_ks_error, map_ks_result}; +use crate::keymaster::key_parameter; +use crate::keymaster::utils::{key_creation_result_to_aidl, key_param_to_aidl}; use crate::keymint::{clock, sdd, soft}; use crate::{ android::hardware::security::keymint::ErrorCode::ErrorCode, @@ -51,10 +53,11 @@ use kmr_crypto_boring::rng::BoringRng; use kmr_crypto_boring::rsa::BoringRsa; use kmr_ta::device::CsrSigningAlgorithm; use kmr_ta::{HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; -use kmr_wire::keymint::KeyParam; +use kmr_wire::keymint::{AttestationKey, KeyParam}; use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; use kmr_wire::*; use log::error; +use rand::rand_core::le; use rsbinder::{ExceptionCode, Interface, Status, Strong}; /// Wrapper for operating directly on a KeyMint device. @@ -286,7 +289,7 @@ impl KeyMintDevice { F: Fn(&[u8]) -> Result, { let (f_result, upgraded_blob) = crate::keymaster::utils::upgrade_keyblob_if_required_with( - &self.km_dev, + self.security_level.clone(), self.version(), &key_blob, &[], @@ -350,13 +353,15 @@ impl KeyMintDevice { } } -static mut KM_STRONGBOX: OnceLock = OnceLock::new(); +use std::sync::Mutex; + +static KM_STRONGBOX: OnceLock> = OnceLock::new(); -static mut KM_TEE: OnceLock = OnceLock::new(); +static KM_TEE: OnceLock> = OnceLock::new(); -static mut KM_WRAPPER_STRONGBOX: OnceLock = OnceLock::new(); +static KM_WRAPPER_STRONGBOX: OnceLock> = OnceLock::new(); -static mut KM_WRAPPER_TEE: OnceLock = OnceLock::new(); +static KM_WRAPPER_TEE: OnceLock> = OnceLock::new(); pub struct KeyMintWrapper { security_level: SecurityLevel, @@ -429,51 +434,220 @@ impl IKeyMintDevice for KeyMintWrapper { crate::android::hardware::security::keymint::KeyMintHardwareInfo::KeyMintHardwareInfo, Status, > { - todo!() + let hardware_info: keymint::KeyMintHardwareInfo = get_keymint_device(self.security_level) + .unwrap() + .get_hardware_info() + .unwrap(); + + let resp = + crate::android::hardware::security::keymint::KeyMintHardwareInfo::KeyMintHardwareInfo { + securityLevel: match hardware_info.security_level { + kmr_wire::keymint::SecurityLevel::Software => SecurityLevel::SOFTWARE, + kmr_wire::keymint::SecurityLevel::TrustedEnvironment => { + SecurityLevel::TRUSTED_ENVIRONMENT + } + kmr_wire::keymint::SecurityLevel::Strongbox => SecurityLevel::STRONGBOX, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }, + versionNumber: hardware_info.version_number, + keyMintName: hardware_info.key_mint_name, + keyMintAuthorName: hardware_info.key_mint_author_name, + timestampTokenRequired: hardware_info.timestamp_token_required, + }; + + Result::Ok(resp) } - fn addRngEntropy(&self, _arg_data: &[u8]) -> rsbinder::status::Result<()> { - todo!() + fn addRngEntropy(&self, data: &[u8]) -> rsbinder::status::Result<()> { + let req = PerformOpReq::DeviceAddRngEntropy(AddRngEntropyRequest { + data: data.to_vec(), + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if result.error_code != 0 { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + Result::Ok(()) } fn generateKey( &self, - _arg_keyParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], - _arg_attestationKey: Option< + keyParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + attestation_key: Option< &crate::android::hardware::security::keymint::AttestationKey::AttestationKey, >, ) -> rsbinder::status::Result< crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, > { - todo!() + let key_parameters: Result> = + keyParams.iter().cloned().map(|p| p.to_km()).collect(); + let key_parameters = key_parameters + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(map_ks_error)?; + let attestation_key = if let Some(ak) = attestation_key { + let key_parameters: Result> = ak + .attestKeyParams + .iter() + .cloned() + .map(|p| p.to_km()) + .collect(); + + let key_parameters = key_parameters + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(map_ks_error)?; + Some(AttestationKey { + key_blob: ak.keyBlob.clone(), + attest_key_params: key_parameters, + issuer_subject_name: ak.issuerSubjectName.clone(), + }) + } else { + None + }; + + let req = PerformOpReq::DeviceGenerateKey(GenerateKeyRequest { + key_params: key_parameters, + attestation_key, + }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + let result = match result.rsp.unwrap() { + PerformOpRsp::DeviceGenerateKey(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + let resp = key_creation_result_to_aidl(result)?; + + Result::Ok(resp) } fn importKey( &self, - _arg_keyParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], - _arg_keyFormat: crate::android::hardware::security::keymint::KeyFormat::KeyFormat, - _arg_keyData: &[u8], - _arg_attestationKey: Option< + key_params: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + key_format: crate::android::hardware::security::keymint::KeyFormat::KeyFormat, + key_data: &[u8], + attestation_key: Option< &crate::android::hardware::security::keymint::AttestationKey::AttestationKey, >, ) -> rsbinder::status::Result< crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, > { - todo!() + let key_parameters: Result> = + key_params.iter().cloned().map(|p| p.to_km()).collect(); + let key_parameters = key_parameters + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(map_ks_error)?; + let attestation_key = if let Some(ak) = attestation_key { + let key_parameters: Result> = ak + .attestKeyParams + .iter() + .cloned() + .map(|p| p.to_km()) + .collect(); + + let key_parameters = key_parameters + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(map_ks_error)?; + Some(AttestationKey { + key_blob: ak.keyBlob.clone(), + attest_key_params: key_parameters, + issuer_subject_name: ak.issuerSubjectName.clone(), + }) + } else { + None + }; + + let key_format = kmr_wire::keymint::KeyFormat::try_from(key_format.0) + .map_err(|_| Status::new_service_specific_error(ErrorCode::INVALID_ARGUMENT.0, None))?; + + let req = PerformOpReq::DeviceImportKey(ImportKeyRequest { + key_params: key_parameters, + key_format, + key_data: key_data.to_vec(), + attestation_key, + }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + let result = match result.rsp.unwrap() { + PerformOpRsp::DeviceImportKey(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + let resp = key_creation_result_to_aidl(result)?; + + Result::Ok(resp) } fn importWrappedKey( &self, - _arg_wrappedKeyData: &[u8], - _arg_wrappingKeyBlob: &[u8], - _arg_maskingKey: &[u8], - _arg_unwrappingParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], - _arg_passwordSid: i64, - _arg_biometricSid: i64, + wrapped_key_data: &[u8], + wrapping_key_blob: &[u8], + masking_key: &[u8], + unwrapping_params: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + password_sid: i64, + biometric_sid: i64, ) -> rsbinder::status::Result< crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, > { - todo!() + let unwrapping_params: Result> = + unwrapping_params.iter().cloned().map(|p| p.to_km()).collect(); + let unwrapping_params = unwrapping_params + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(map_ks_error)?; + + let req = PerformOpReq::DeviceImportWrappedKey(ImportWrappedKeyRequest { + wrapped_key_data: wrapped_key_data.to_vec(), + wrapping_key_blob: wrapping_key_blob.to_vec(), + masking_key: masking_key.to_vec(), + unwrapping_params, + password_sid, + biometric_sid, + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + let result = match result.rsp.unwrap() { + PerformOpRsp::DeviceImportWrappedKey(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + let resp = key_creation_result_to_aidl(result)?; + + Result::Ok(resp) } fn upgradeKey( @@ -838,27 +1012,27 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { Ok(KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev)) } -pub fn get_keymint_device<'a>(security_level: SecurityLevel) -> Result<&'a mut KeyMintTa> { +pub fn get_keymint_device<'a>( + security_level: SecurityLevel, +) -> Result> { match security_level { SecurityLevel::STRONGBOX => { - let strongbox = unsafe { - KM_STRONGBOX.get_mut_or_init(|| { + let strongbox = KM_STRONGBOX.get_or_init(|| { + Mutex::new( init_keymint_ta(security_level) - .expect(err!("Failed to init strongbox wrapper").as_str()) - }) - }; - - Ok(strongbox) + .expect(err!("Failed to init strongbox wrapper").as_str()), + ) + }); + Ok(strongbox.lock().expect("Failed to lock KM_STRONGBOX")) } SecurityLevel::TRUSTED_ENVIRONMENT => { - let tee = unsafe { - KM_TEE.get_mut_or_init(|| { + let tee = KM_TEE.get_or_init(|| { + Mutex::new( init_keymint_ta(security_level) - .expect(err!("Failed to init tee wrapper").as_str()) - }) - }; - - Ok(tee) + .expect(err!("Failed to init tee wrapper").as_str()), + ) + }); + Ok(tee.lock().expect("Failed to lock KM_TEE")) } SecurityLevel::SOFTWARE => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) .context(err!("Software KeyMint not supported")), @@ -867,25 +1041,29 @@ pub fn get_keymint_device<'a>(security_level: SecurityLevel) -> Result<&'a mut K } } -pub fn get_keymint_wrapper<'a>(security_level: SecurityLevel) -> Result<&'a mut KeyMintWrapper> { +pub fn get_keymint_wrapper<'a>( + security_level: SecurityLevel, +) -> Result> { match security_level { SecurityLevel::STRONGBOX => { - let wrapper = unsafe { - KM_WRAPPER_STRONGBOX.get_mut_or_init(|| { + let wrapper = KM_WRAPPER_STRONGBOX.get_or_init(|| { + Mutex::new( KeyMintWrapper::new(security_level) - .expect(err!("Failed to init strongbox wrapper").as_str()) - }) - }; - Ok(wrapper) + .expect(err!("Failed to init strongbox wrapper").as_str()), + ) + }); + + Ok(wrapper.lock().expect("Failed to lock KM_WRAPPER_STRONGBOX")) } SecurityLevel::TRUSTED_ENVIRONMENT => { - let wrapper = unsafe { - KM_WRAPPER_TEE.get_mut_or_init(|| { + let wrapper = KM_WRAPPER_TEE.get_or_init(|| { + Mutex::new( KeyMintWrapper::new(security_level) - .expect(err!("Failed to init tee wrapper").as_str()) - }) - }; - Ok(wrapper) + .expect(err!("Failed to init tee wrapper").as_str()), + ) + }); + + Ok(wrapper.lock().expect("Failed to lock KM_WRAPPER_TEE")) } SecurityLevel::SOFTWARE => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) .context(err!("Software KeyMint not supported")), diff --git a/libs/rust/src/keymaster/keymint_operation.rs b/src/keymaster/keymint_operation.rs similarity index 95% rename from libs/rust/src/keymaster/keymint_operation.rs rename to src/keymaster/keymint_operation.rs index 0c737fa..20557e9 100644 --- a/libs/rust/src/keymaster/keymint_operation.rs +++ b/src/keymaster/keymint_operation.rs @@ -1,15 +1,12 @@ use kmr_wire::keymint::KeyParam; -use rsbinder::{Interface, Strong}; +use rsbinder::Interface; use crate::{ android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, IKeyMintOperation::IKeyMintOperation, SecurityLevel::SecurityLevel, }, - keymaster::{ - error::map_ks_error, - keymint_device::{get_keymint_wrapper, KeyMintWrapper}, - }, + keymaster::{error::map_ks_error, keymint_device::get_keymint_wrapper}, }; pub struct KeyMintOperation { diff --git a/libs/rust/src/keymaster/metrics_store.rs b/src/keymaster/metrics_store.rs similarity index 100% rename from libs/rust/src/keymaster/metrics_store.rs rename to src/keymaster/metrics_store.rs diff --git a/libs/rust/src/keymaster/mod.rs b/src/keymaster/mod.rs similarity index 100% rename from libs/rust/src/keymaster/mod.rs rename to src/keymaster/mod.rs diff --git a/libs/rust/src/keymaster/operation.rs b/src/keymaster/operation.rs similarity index 100% rename from libs/rust/src/keymaster/operation.rs rename to src/keymaster/operation.rs diff --git a/libs/rust/src/keymaster/permission.rs b/src/keymaster/permission.rs similarity index 100% rename from libs/rust/src/keymaster/permission.rs rename to src/keymaster/permission.rs diff --git a/libs/rust/src/keymaster/security_level.rs b/src/keymaster/security_level.rs similarity index 97% rename from libs/rust/src/keymaster/security_level.rs rename to src/keymaster/security_level.rs index dc9b0cf..8dbe767 100644 --- a/libs/rust/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -48,22 +48,20 @@ use rsbinder::{thread_state::CallingContext, Interface, Status}; // Blob of 32 zeroes used as empty masking key. static ZERO_BLOB_32: &[u8] = &[0; 32]; -pub struct KeystoreSecurityLevel<'a> { +pub struct KeystoreSecurityLevel { security_level: SecurityLevel, hw_info: HardwareInfo, km_uuid: Uuid, operation_db: OperationDb, - keymint: &'a dyn IKeyMintDevice, } -impl<'a> KeystoreSecurityLevel<'a> { +impl KeystoreSecurityLevel { pub fn new(security_level: SecurityLevel, hw_info: HardwareInfo, km_uuid: Uuid) -> Self { KeystoreSecurityLevel { security_level, hw_info, km_uuid, operation_db: OperationDb::new(), - keymint: get_keymint_wrapper(security_level).unwrap(), } } @@ -220,7 +218,7 @@ impl<'a> KeystoreSecurityLevel<'a> { F: Fn(&[u8]) -> Result, { let (v, upgraded_blob) = crate::keymaster::utils::upgrade_keyblob_if_required_with( - get_keymint_wrapper(self.security_level).unwrap(), + self.security_level, self.hw_info.version_number, key_blob, params, @@ -431,7 +429,9 @@ impl<'a> KeystoreSecurityLevel<'a> { ), 5000, // Generate can take a little longer. ); - let result = self.keymint.generateKey(¶ms, attest_key.as_ref()); + let result = get_keymint_wrapper(self.security_level) + .unwrap() + .generateKey(¶ms, attest_key.as_ref()); map_binder_status(result) }, ) @@ -449,7 +449,9 @@ impl<'a> KeystoreSecurityLevel<'a> { ), 5000, // Generate can take a little longer. ); - self.keymint.generateKey(¶ms, None) + get_keymint_wrapper(self.security_level) + .unwrap() + .generateKey(¶ms, None) } .context(err!( "While generating without a provided \ @@ -513,7 +515,7 @@ impl<'a> KeystoreSecurityLevel<'a> { }) .context(err!())?; - let km_dev = &self.keymint; + let km_dev = get_keymint_wrapper(self.security_level).unwrap(); let creation_result = map_binder_status({ let _wp = self.watch("KeystoreSecurityLevel::import_key: calling IKeyMintDevice::importKey."); @@ -645,7 +647,8 @@ impl<'a> KeystoreSecurityLevel<'a> { let _wp = self.watch( "KeystoreSecurityLevel::import_wrapped_key: calling IKeyMintDevice::importWrappedKey.", ); - let creation_result = map_binder_status(self.keymint.importWrappedKey( + let km_dev = get_keymint_wrapper(self.security_level).unwrap(); + let creation_result = map_binder_status(km_dev.importWrappedKey( wrapped_data, wrapping_blob, masking_key, @@ -680,7 +683,7 @@ impl<'a> KeystoreSecurityLevel<'a> { // check_key_permission(KeyPerm::ConvertStorageKeyToEphemeral, storage_key, &None) // .context(err!("Check permission"))?; - let km_dev = &self.keymint; + let km_dev = get_keymint_wrapper(self.security_level).unwrap(); let res = { let _wp = self.watch(concat!( "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", @@ -839,7 +842,8 @@ impl<'a> KeystoreSecurityLevel<'a> { let _wp = self.watch( "KeystoreSecurityLevel::create_operation: calling IKeyMintDevice::begin", ); - self.keymint.begin( + let km_dev = get_keymint_wrapper(self.security_level).unwrap(); + km_dev.begin( purpose, blob, operation_parameters, @@ -934,7 +938,7 @@ impl<'a> KeystoreSecurityLevel<'a> { // check_key_permission(KeyPerm::Delete, key, &None) // .context(err!("delete_key: Checking delete permissions"))?; - let km_dev = &self.keymint; + let km_dev = get_keymint_wrapper(self.security_level).unwrap(); { let _wp = self.watch("KeystoreSecuritylevel::delete_key: calling IKeyMintDevice::deleteKey"); @@ -943,9 +947,9 @@ impl<'a> KeystoreSecurityLevel<'a> { } } -impl<'a> Interface for KeystoreSecurityLevel<'a> {} +impl Interface for KeystoreSecurityLevel {} -impl<'a> IKeystoreSecurityLevel for KeystoreSecurityLevel<'a> { +impl IKeystoreSecurityLevel for KeystoreSecurityLevel { fn createOperation( &self, key: &KeyDescriptor, diff --git a/libs/rust/src/keymaster/super_key.rs b/src/keymaster/super_key.rs similarity index 100% rename from libs/rust/src/keymaster/super_key.rs rename to src/keymaster/super_key.rs diff --git a/libs/rust/src/keymaster/utils.rs b/src/keymaster/utils.rs similarity index 58% rename from libs/rust/src/keymaster/utils.rs rename to src/keymaster/utils.rs index a75d62a..3461efe 100644 --- a/libs/rust/src/keymaster/utils.rs +++ b/src/keymaster/utils.rs @@ -1,12 +1,19 @@ +use std::result; + use crate::{ - android::hardware::security::keymint::{ - ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, - KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, - KeyParameterValue::KeyParameterValue, + android::hardware::security::{ + self, + keymint::{ + ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, + KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, + KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, + }, }, consts, err, keymaster::{ - error::KsError as Error, key_parameter::KeyParameter, keymint_device::KeyMintDevice, + error::{KsError as Error, map_ks_error}, + key_parameter::KeyParameter, + keymint_device::{KeyMintDevice, get_keymint_wrapper}, }, watchdog, }; @@ -41,7 +48,7 @@ pub fn key_characteristics_to_internal( /// Upgrade a keyblob then invoke both the `new_blob_handler` and the `km_op` closures. On success /// a tuple of the `km_op`s result and the optional upgraded blob is returned. fn upgrade_keyblob_and_perform_op( - km_dev: &dyn IKeyMintDevice, + security_level: SecurityLevel, key_blob: &[u8], upgrade_params: &[KmKeyParameter], km_op: KmOp, @@ -51,6 +58,7 @@ where KmOp: Fn(&[u8]) -> Result, NewBlobHandler: FnOnce(&[u8]) -> Result<()>, { + let km_dev = get_keymint_wrapper(security_level).unwrap(); let upgraded_blob = { let _wp = watchdog::watch( "utils::upgrade_keyblob_and_perform_op: calling IKeyMintDevice::upgradeKey.", @@ -73,7 +81,7 @@ where /// upgraded blob as argument. On success a tuple of the `km_op`s result and the /// optional upgraded blob is returned. pub fn upgrade_keyblob_if_required_with( - km_dev: &dyn IKeyMintDevice, + security_level: SecurityLevel, km_dev_version: i32, key_blob: &[u8], upgrade_params: &[KmKeyParameter], @@ -86,7 +94,7 @@ where { match km_op(key_blob) { Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => upgrade_keyblob_and_perform_op( - km_dev, + security_level, key_blob, upgrade_params, km_op, @@ -115,7 +123,7 @@ where ); let inner_keyblob = &key_blob[consts::KEYMASTER_BLOB_HW_PREFIX.len()..]; upgrade_keyblob_and_perform_op( - km_dev, + security_level, inner_keyblob, upgrade_params, km_op, @@ -598,3 +606,338 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { } } } + +pub fn key_creation_result_to_aidl( + result: kmr_wire::keymint::KeyCreationResult, +) -> Result { + + let certificates: Vec< + crate::android::hardware::security::keymint::Certificate::Certificate, + > = result + .certificate_chain + .iter() + .map( + |c| crate::android::hardware::security::keymint::Certificate::Certificate { + encodedCertificate: c.encoded_certificate.clone(), + }, + ) + .collect(); + + let key_characteristics: Result, rsbinder::Status> = result.key_characteristics.iter().map(|kc| { + let params: Result, rsbinder::Status> = kc.authorizations.iter().map(|p| { + key_param_to_aidl(p.clone()) + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(|e| map_ks_error(e)) + }).collect(); + let params = params?; + + Result::Ok(crate::android::hardware::security::keymint::KeyCharacteristics::KeyCharacteristics { + authorizations: params, + securityLevel: match kc.security_level { + kmr_wire::keymint::SecurityLevel::Software => SecurityLevel::SOFTWARE, + kmr_wire::keymint::SecurityLevel::TrustedEnvironment => { + SecurityLevel::TRUSTED_ENVIRONMENT + } + kmr_wire::keymint::SecurityLevel::Strongbox => SecurityLevel::STRONGBOX, + _ => { + return Err(rsbinder::Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }, + }) + + }).collect(); + let key_characteristics = key_characteristics?; + + let resp = + crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult { + keyBlob: result.key_blob, + keyCharacteristics: key_characteristics, + certificateChain: certificates, + }; + + Result::Ok(resp) +} + +pub fn key_param_to_aidl( + kp: KeyParam, +) -> Result { + let tag = match kp.tag() { + keymint::Tag::Invalid => crate::android::hardware::security::keymint::Tag::Tag::INVALID, + keymint::Tag::Purpose => crate::android::hardware::security::keymint::Tag::Tag::PURPOSE, + keymint::Tag::Algorithm => crate::android::hardware::security::keymint::Tag::Tag::ALGORITHM, + keymint::Tag::KeySize => crate::android::hardware::security::keymint::Tag::Tag::KEY_SIZE, + keymint::Tag::BlockMode => { + crate::android::hardware::security::keymint::Tag::Tag::BLOCK_MODE + } + keymint::Tag::Digest => crate::android::hardware::security::keymint::Tag::Tag::DIGEST, + keymint::Tag::Padding => crate::android::hardware::security::keymint::Tag::Tag::PADDING, + keymint::Tag::CallerNonce => { + crate::android::hardware::security::keymint::Tag::Tag::CALLER_NONCE + } + keymint::Tag::MinMacLength => { + crate::android::hardware::security::keymint::Tag::Tag::MIN_MAC_LENGTH + } + keymint::Tag::EcCurve => crate::android::hardware::security::keymint::Tag::Tag::EC_CURVE, + keymint::Tag::RsaPublicExponent => { + crate::android::hardware::security::keymint::Tag::Tag::RSA_PUBLIC_EXPONENT + } + keymint::Tag::IncludeUniqueId => { + crate::android::hardware::security::keymint::Tag::Tag::INCLUDE_UNIQUE_ID + } + keymint::Tag::RsaOaepMgfDigest => { + crate::android::hardware::security::keymint::Tag::Tag::RSA_OAEP_MGF_DIGEST + } + keymint::Tag::BootloaderOnly => { + crate::android::hardware::security::keymint::Tag::Tag::BOOTLOADER_ONLY + } + keymint::Tag::RollbackResistance => { + crate::android::hardware::security::keymint::Tag::Tag::ROLLBACK_RESISTANCE + } + keymint::Tag::HardwareType => { + crate::android::hardware::security::keymint::Tag::Tag::HARDWARE_TYPE + } + keymint::Tag::EarlyBootOnly => { + crate::android::hardware::security::keymint::Tag::Tag::EARLY_BOOT_ONLY + } + keymint::Tag::ActiveDatetime => { + crate::android::hardware::security::keymint::Tag::Tag::ACTIVE_DATETIME + } + keymint::Tag::OriginationExpireDatetime => { + crate::android::hardware::security::keymint::Tag::Tag::ORIGINATION_EXPIRE_DATETIME + } + keymint::Tag::UsageExpireDatetime => { + crate::android::hardware::security::keymint::Tag::Tag::USAGE_EXPIRE_DATETIME + } + keymint::Tag::MinSecondsBetweenOps => { + crate::android::hardware::security::keymint::Tag::Tag::MIN_SECONDS_BETWEEN_OPS + } + keymint::Tag::MaxUsesPerBoot => { + crate::android::hardware::security::keymint::Tag::Tag::MAX_USES_PER_BOOT + } + keymint::Tag::UsageCountLimit => { + crate::android::hardware::security::keymint::Tag::Tag::USAGE_COUNT_LIMIT + } + keymint::Tag::UserId => crate::android::hardware::security::keymint::Tag::Tag::USER_ID, + keymint::Tag::UserSecureId => { + crate::android::hardware::security::keymint::Tag::Tag::USER_SECURE_ID + } + keymint::Tag::NoAuthRequired => { + crate::android::hardware::security::keymint::Tag::Tag::NO_AUTH_REQUIRED + } + keymint::Tag::UserAuthType => { + crate::android::hardware::security::keymint::Tag::Tag::USER_AUTH_TYPE + } + keymint::Tag::AuthTimeout => { + crate::android::hardware::security::keymint::Tag::Tag::AUTH_TIMEOUT + } + keymint::Tag::AllowWhileOnBody => { + crate::android::hardware::security::keymint::Tag::Tag::ALLOW_WHILE_ON_BODY + } + keymint::Tag::TrustedUserPresenceRequired => { + crate::android::hardware::security::keymint::Tag::Tag::TRUSTED_USER_PRESENCE_REQUIRED + } + keymint::Tag::TrustedConfirmationRequired => { + crate::android::hardware::security::keymint::Tag::Tag::TRUSTED_CONFIRMATION_REQUIRED + } + keymint::Tag::UnlockedDeviceRequired => { + crate::android::hardware::security::keymint::Tag::Tag::UNLOCKED_DEVICE_REQUIRED + } + keymint::Tag::ApplicationId => { + crate::android::hardware::security::keymint::Tag::Tag::APPLICATION_ID + } + keymint::Tag::ApplicationData => { + crate::android::hardware::security::keymint::Tag::Tag::APPLICATION_DATA + } + keymint::Tag::CreationDatetime => { + crate::android::hardware::security::keymint::Tag::Tag::CREATION_DATETIME + } + keymint::Tag::Origin => crate::android::hardware::security::keymint::Tag::Tag::ORIGIN, + keymint::Tag::RootOfTrust => { + crate::android::hardware::security::keymint::Tag::Tag::ROOT_OF_TRUST + } + keymint::Tag::OsVersion => { + crate::android::hardware::security::keymint::Tag::Tag::OS_VERSION + } + keymint::Tag::OsPatchlevel => { + crate::android::hardware::security::keymint::Tag::Tag::OS_PATCHLEVEL + } + keymint::Tag::UniqueId => crate::android::hardware::security::keymint::Tag::Tag::UNIQUE_ID, + keymint::Tag::AttestationChallenge => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_CHALLENGE + } + keymint::Tag::AttestationApplicationId => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_APPLICATION_ID + } + keymint::Tag::AttestationIdBrand => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_BRAND + } + keymint::Tag::AttestationIdDevice => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_DEVICE + } + keymint::Tag::AttestationIdProduct => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_PRODUCT + } + keymint::Tag::AttestationIdSerial => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_SERIAL + } + keymint::Tag::AttestationIdImei => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_IMEI + } + keymint::Tag::AttestationIdMeid => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_MEID + } + keymint::Tag::AttestationIdManufacturer => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_MANUFACTURER + } + keymint::Tag::AttestationIdModel => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_MODEL + } + keymint::Tag::VendorPatchlevel => { + crate::android::hardware::security::keymint::Tag::Tag::VENDOR_PATCHLEVEL + } + keymint::Tag::BootPatchlevel => { + crate::android::hardware::security::keymint::Tag::Tag::BOOT_PATCHLEVEL + } + keymint::Tag::DeviceUniqueAttestation => { + crate::android::hardware::security::keymint::Tag::Tag::DEVICE_UNIQUE_ATTESTATION + } + keymint::Tag::IdentityCredentialKey => { + crate::android::hardware::security::keymint::Tag::Tag::IDENTITY_CREDENTIAL_KEY + } + keymint::Tag::StorageKey => { + crate::android::hardware::security::keymint::Tag::Tag::STORAGE_KEY + } + keymint::Tag::AttestationIdSecondImei => { + crate::android::hardware::security::keymint::Tag::Tag::ATTESTATION_ID_SECOND_IMEI + } + keymint::Tag::AssociatedData => { + crate::android::hardware::security::keymint::Tag::Tag::ASSOCIATED_DATA + } + keymint::Tag::Nonce => crate::android::hardware::security::keymint::Tag::Tag::NONCE, + keymint::Tag::MacLength => { + crate::android::hardware::security::keymint::Tag::Tag::MAC_LENGTH + } + keymint::Tag::ResetSinceIdRotation => { + crate::android::hardware::security::keymint::Tag::Tag::RESET_SINCE_ID_ROTATION + } + keymint::Tag::ConfirmationToken => { + crate::android::hardware::security::keymint::Tag::Tag::CONFIRMATION_TOKEN + } + keymint::Tag::CertificateSerial => { + crate::android::hardware::security::keymint::Tag::Tag::CERTIFICATE_SERIAL + } + keymint::Tag::CertificateSubject => { + crate::android::hardware::security::keymint::Tag::Tag::CERTIFICATE_SUBJECT + } + keymint::Tag::CertificateNotBefore => { + crate::android::hardware::security::keymint::Tag::Tag::CERTIFICATE_NOT_BEFORE + } + keymint::Tag::CertificateNotAfter => { + crate::android::hardware::security::keymint::Tag::Tag::CERTIFICATE_NOT_AFTER + } + keymint::Tag::MaxBootLevel => { + crate::android::hardware::security::keymint::Tag::Tag::MAX_BOOT_LEVEL + } + keymint::Tag::ModuleHash => { + crate::android::hardware::security::keymint::Tag::Tag::MODULE_HASH + } + }; + + let value = match kp { + KeyParam::Purpose(v) => KeyParameterValue::KeyPurpose( + crate::android::hardware::security::keymint::KeyPurpose::KeyPurpose(v as i32), + ), + KeyParam::Algorithm(v) => KeyParameterValue::Algorithm( + crate::android::hardware::security::keymint::Algorithm::Algorithm(v as i32), + ), + KeyParam::KeySize(KeySizeInBits(v)) => KeyParameterValue::Integer(v as i32), + KeyParam::BlockMode(v) => KeyParameterValue::BlockMode( + crate::android::hardware::security::keymint::BlockMode::BlockMode(v as i32), + ), + KeyParam::Digest(v) => KeyParameterValue::Digest( + crate::android::hardware::security::keymint::Digest::Digest(v as i32), + ), + KeyParam::Padding(v) => KeyParameterValue::PaddingMode( + crate::android::hardware::security::keymint::PaddingMode::PaddingMode(v as i32), + ), + KeyParam::CallerNonce => KeyParameterValue::BoolValue(true), + KeyParam::MinMacLength(v) => KeyParameterValue::Integer(v as i32), + KeyParam::EcCurve(v) => KeyParameterValue::EcCurve( + crate::android::hardware::security::keymint::EcCurve::EcCurve(v as i32), + ), + KeyParam::RsaPublicExponent(kmr_wire::RsaExponent(v)) => { + KeyParameterValue::LongInteger(v as i64) + } + KeyParam::IncludeUniqueId => KeyParameterValue::BoolValue(true), + KeyParam::RsaOaepMgfDigest(v) => KeyParameterValue::Digest( + crate::android::hardware::security::keymint::Digest::Digest(v as i32), + ), + KeyParam::BootloaderOnly => KeyParameterValue::BoolValue(true), + KeyParam::RollbackResistance => KeyParameterValue::BoolValue(true), + KeyParam::EarlyBootOnly => KeyParameterValue::BoolValue(true), + KeyParam::ActiveDatetime(kmr_wire::keymint::DateTime { ms_since_epoch }) => { + KeyParameterValue::DateTime(ms_since_epoch) + } + KeyParam::OriginationExpireDatetime(kmr_wire::keymint::DateTime { ms_since_epoch }) => { + KeyParameterValue::DateTime(ms_since_epoch) + } + KeyParam::UsageExpireDatetime(kmr_wire::keymint::DateTime { ms_since_epoch }) => { + KeyParameterValue::DateTime(ms_since_epoch) + } + KeyParam::MaxUsesPerBoot(v) => KeyParameterValue::Integer(v as i32), + KeyParam::UsageCountLimit(v) => KeyParameterValue::Integer(v as i32), + KeyParam::UserId(v) => KeyParameterValue::Integer(v as i32), + KeyParam::UserSecureId(v) => KeyParameterValue::LongInteger(v as i64), + KeyParam::NoAuthRequired => KeyParameterValue::BoolValue(true), + KeyParam::UserAuthType(v) => KeyParameterValue::Integer(v as i32), + KeyParam::AuthTimeout(v) => KeyParameterValue::Integer(v as i32), + KeyParam::AllowWhileOnBody => KeyParameterValue::BoolValue(true), + KeyParam::TrustedUserPresenceRequired => KeyParameterValue::BoolValue(true), + KeyParam::TrustedConfirmationRequired => KeyParameterValue::BoolValue(true), + KeyParam::UnlockedDeviceRequired => KeyParameterValue::BoolValue(true), + KeyParam::ApplicationId(v) => KeyParameterValue::Blob(v), + KeyParam::ApplicationData(v) => KeyParameterValue::Blob(v), + KeyParam::CreationDatetime(kmr_wire::keymint::DateTime { ms_since_epoch }) => { + KeyParameterValue::DateTime(ms_since_epoch) + } + KeyParam::Origin(v) => KeyParameterValue::Origin( + crate::android::hardware::security::keymint::KeyOrigin::KeyOrigin(v as i32), + ), + KeyParam::RootOfTrust(v) => KeyParameterValue::Blob(v), + KeyParam::OsVersion(v) => KeyParameterValue::Integer(v as i32), + KeyParam::OsPatchlevel(v) => KeyParameterValue::Integer(v as i32), + KeyParam::AttestationChallenge(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationApplicationId(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdBrand(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdDevice(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdProduct(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdSerial(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdImei(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdMeid(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdManufacturer(v) => KeyParameterValue::Blob(v), + KeyParam::AttestationIdModel(v) => KeyParameterValue::Blob(v), + KeyParam::VendorPatchlevel(v) => KeyParameterValue::Integer(v as i32), + KeyParam::BootPatchlevel(v) => KeyParameterValue::Integer(v as i32), + KeyParam::DeviceUniqueAttestation => KeyParameterValue::BoolValue(true), + KeyParam::StorageKey => KeyParameterValue::BoolValue(true), + KeyParam::AttestationIdSecondImei(v) => KeyParameterValue::Blob(v), + KeyParam::Nonce(v) => KeyParameterValue::Blob(v), + KeyParam::MacLength(v) => KeyParameterValue::Integer(v as i32), + KeyParam::ResetSinceIdRotation => KeyParameterValue::BoolValue(true), + KeyParam::CertificateSerial(v) => KeyParameterValue::Blob(v), + KeyParam::CertificateSubject(v) => KeyParameterValue::Blob(v), + KeyParam::CertificateNotBefore(kmr_wire::keymint::DateTime { ms_since_epoch }) => { + KeyParameterValue::DateTime(ms_since_epoch) + } + KeyParam::CertificateNotAfter(kmr_wire::keymint::DateTime { ms_since_epoch }) => { + KeyParameterValue::DateTime(ms_since_epoch) + } + KeyParam::MaxBootLevel(v) => KeyParameterValue::Integer(v as i32), + KeyParam::ModuleHash(v) => KeyParameterValue::Blob(v), + }; + + Ok(crate::android::hardware::security::keymint::KeyParameter::KeyParameter { tag, value }) +} diff --git a/libs/rust/src/keymint/attest.rs b/src/keymint/attest.rs similarity index 100% rename from libs/rust/src/keymint/attest.rs rename to src/keymint/attest.rs diff --git a/libs/rust/src/keymint/clock.rs b/src/keymint/clock.rs similarity index 100% rename from libs/rust/src/keymint/clock.rs rename to src/keymint/clock.rs diff --git a/libs/rust/src/keymint/mod.rs b/src/keymint/mod.rs similarity index 100% rename from libs/rust/src/keymint/mod.rs rename to src/keymint/mod.rs diff --git a/libs/rust/src/keymint/rpc.rs b/src/keymint/rpc.rs similarity index 100% rename from libs/rust/src/keymint/rpc.rs rename to src/keymint/rpc.rs diff --git a/libs/rust/src/keymint/sdd.rs b/src/keymint/sdd.rs similarity index 100% rename from libs/rust/src/keymint/sdd.rs rename to src/keymint/sdd.rs diff --git a/libs/rust/src/keymint/soft.rs b/src/keymint/soft.rs similarity index 100% rename from libs/rust/src/keymint/soft.rs rename to src/keymint/soft.rs diff --git a/libs/rust/src/lib.rs b/src/lib.rs similarity index 100% rename from libs/rust/src/lib.rs rename to src/lib.rs diff --git a/libs/rust/src/logging.rs b/src/logging.rs similarity index 100% rename from libs/rust/src/logging.rs rename to src/logging.rs diff --git a/libs/rust/src/macros.rs b/src/macros.rs similarity index 100% rename from libs/rust/src/macros.rs rename to src/macros.rs diff --git a/libs/rust/src/main.rs b/src/main.rs similarity index 100% rename from libs/rust/src/main.rs rename to src/main.rs diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml deleted file mode 100644 index 568741e..0000000 --- a/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/main/java/top/qwq2333/ohmykeymint/placeholder b/src/main/java/top/qwq2333/ohmykeymint/placeholder deleted file mode 100644 index 40816a2..0000000 --- a/src/main/java/top/qwq2333/ohmykeymint/placeholder +++ /dev/null @@ -1 +0,0 @@ -Hi \ No newline at end of file diff --git a/libs/rust/src/plat/mod.rs b/src/plat/mod.rs similarity index 100% rename from libs/rust/src/plat/mod.rs rename to src/plat/mod.rs diff --git a/libs/rust/src/plat/property_watcher.rs b/src/plat/property_watcher.rs similarity index 100% rename from libs/rust/src/plat/property_watcher.rs rename to src/plat/property_watcher.rs diff --git a/libs/rust/src/plat/utils.rs b/src/plat/utils.rs similarity index 100% rename from libs/rust/src/plat/utils.rs rename to src/plat/utils.rs diff --git a/libs/rust/src/utils.rs b/src/utils.rs similarity index 100% rename from libs/rust/src/utils.rs rename to src/utils.rs diff --git a/libs/rust/src/watchdog.rs b/src/watchdog.rs similarity index 100% rename from libs/rust/src/watchdog.rs rename to src/watchdog.rs diff --git a/libs/rust/ta/Cargo.toml b/ta/Cargo.toml similarity index 100% rename from libs/rust/ta/Cargo.toml rename to ta/Cargo.toml diff --git a/libs/rust/ta/fuzz/.gitignore b/ta/fuzz/.gitignore similarity index 100% rename from libs/rust/ta/fuzz/.gitignore rename to ta/fuzz/.gitignore diff --git a/libs/rust/ta/fuzz/Cargo.toml b/ta/fuzz/Cargo.toml similarity index 100% rename from libs/rust/ta/fuzz/Cargo.toml rename to ta/fuzz/Cargo.toml diff --git a/libs/rust/ta/fuzz/fuzz_targets/keydescription.rs b/ta/fuzz/fuzz_targets/keydescription.rs similarity index 100% rename from libs/rust/ta/fuzz/fuzz_targets/keydescription.rs rename to ta/fuzz/fuzz_targets/keydescription.rs diff --git a/libs/rust/ta/src/cert.rs b/ta/src/cert.rs similarity index 100% rename from libs/rust/ta/src/cert.rs rename to ta/src/cert.rs diff --git a/libs/rust/ta/src/clock.rs b/ta/src/clock.rs similarity index 100% rename from libs/rust/ta/src/clock.rs rename to ta/src/clock.rs diff --git a/libs/rust/ta/src/device.rs b/ta/src/device.rs similarity index 100% rename from libs/rust/ta/src/device.rs rename to ta/src/device.rs diff --git a/libs/rust/ta/src/keys.rs b/ta/src/keys.rs similarity index 100% rename from libs/rust/ta/src/keys.rs rename to ta/src/keys.rs diff --git a/libs/rust/ta/src/lib.rs b/ta/src/lib.rs similarity index 99% rename from libs/rust/ta/src/lib.rs rename to ta/src/lib.rs index 44b16b2..9a49ab8 100644 --- a/libs/rust/ta/src/lib.rs +++ b/ta/src/lib.rs @@ -98,6 +98,9 @@ struct AttestationChainInfo { issuer: Vec, } +unsafe impl Send for KeyMintTa {} +unsafe impl Sync for KeyMintTa {} + /// KeyMint device implementation, running in secure environment. pub struct KeyMintTa { /** diff --git a/libs/rust/ta/src/operation.rs b/ta/src/operation.rs similarity index 100% rename from libs/rust/ta/src/operation.rs rename to ta/src/operation.rs diff --git a/libs/rust/ta/src/rkp.rs b/ta/src/rkp.rs similarity index 100% rename from libs/rust/ta/src/rkp.rs rename to ta/src/rkp.rs diff --git a/libs/rust/ta/src/secret.rs b/ta/src/secret.rs similarity index 100% rename from libs/rust/ta/src/secret.rs rename to ta/src/secret.rs diff --git a/libs/rust/ta/src/tests.rs b/ta/src/tests.rs similarity index 100% rename from libs/rust/ta/src/tests.rs rename to ta/src/tests.rs diff --git a/libs/rust/tests/Cargo.toml b/tests/Cargo.toml similarity index 100% rename from libs/rust/tests/Cargo.toml rename to tests/Cargo.toml diff --git a/libs/rust/tests/src/bin/auth-keyblob-parse.rs b/tests/src/bin/auth-keyblob-parse.rs similarity index 100% rename from libs/rust/tests/src/bin/auth-keyblob-parse.rs rename to tests/src/bin/auth-keyblob-parse.rs diff --git a/libs/rust/tests/src/bin/encrypted-keyblob-parse.rs b/tests/src/bin/encrypted-keyblob-parse.rs similarity index 100% rename from libs/rust/tests/src/bin/encrypted-keyblob-parse.rs rename to tests/src/bin/encrypted-keyblob-parse.rs diff --git a/libs/rust/tests/src/lib.rs b/tests/src/lib.rs similarity index 100% rename from libs/rust/tests/src/lib.rs rename to tests/src/lib.rs diff --git a/libs/rust/tests/tests/keyblob_test.rs b/tests/tests/keyblob_test.rs similarity index 100% rename from libs/rust/tests/tests/keyblob_test.rs rename to tests/tests/keyblob_test.rs diff --git a/libs/rust/watchdog/Cargo.toml b/watchdog/Cargo.toml similarity index 100% rename from libs/rust/watchdog/Cargo.toml rename to watchdog/Cargo.toml diff --git a/libs/rust/watchdog/src/lib.rs b/watchdog/src/lib.rs similarity index 100% rename from libs/rust/watchdog/src/lib.rs rename to watchdog/src/lib.rs diff --git a/libs/rust/wire/Cargo.toml b/wire/Cargo.toml similarity index 100% rename from libs/rust/wire/Cargo.toml rename to wire/Cargo.toml diff --git a/libs/rust/wire/fuzz/.gitignore b/wire/fuzz/.gitignore similarity index 100% rename from libs/rust/wire/fuzz/.gitignore rename to wire/fuzz/.gitignore diff --git a/libs/rust/wire/fuzz/Cargo.toml b/wire/fuzz/Cargo.toml similarity index 100% rename from libs/rust/wire/fuzz/Cargo.toml rename to wire/fuzz/Cargo.toml diff --git a/libs/rust/wire/fuzz/fuzz_targets/legacy_message.rs b/wire/fuzz/fuzz_targets/legacy_message.rs similarity index 100% rename from libs/rust/wire/fuzz/fuzz_targets/legacy_message.rs rename to wire/fuzz/fuzz_targets/legacy_message.rs diff --git a/libs/rust/wire/fuzz/fuzz_targets/message.rs b/wire/fuzz/fuzz_targets/message.rs similarity index 100% rename from libs/rust/wire/fuzz/fuzz_targets/message.rs rename to wire/fuzz/fuzz_targets/message.rs diff --git a/libs/rust/wire/src/keymint.rs b/wire/src/keymint.rs similarity index 100% rename from libs/rust/wire/src/keymint.rs rename to wire/src/keymint.rs diff --git a/libs/rust/wire/src/legacy.rs b/wire/src/legacy.rs similarity index 100% rename from libs/rust/wire/src/legacy.rs rename to wire/src/legacy.rs diff --git a/libs/rust/wire/src/lib.rs b/wire/src/lib.rs similarity index 100% rename from libs/rust/wire/src/lib.rs rename to wire/src/lib.rs diff --git a/libs/rust/wire/src/rpc.rs b/wire/src/rpc.rs similarity index 100% rename from libs/rust/wire/src/rpc.rs rename to wire/src/rpc.rs diff --git a/libs/rust/wire/src/secureclock.rs b/wire/src/secureclock.rs similarity index 100% rename from libs/rust/wire/src/secureclock.rs rename to wire/src/secureclock.rs diff --git a/libs/rust/wire/src/sharedsecret.rs b/wire/src/sharedsecret.rs similarity index 100% rename from libs/rust/wire/src/sharedsecret.rs rename to wire/src/sharedsecret.rs diff --git a/libs/rust/wire/src/tests.rs b/wire/src/tests.rs similarity index 100% rename from libs/rust/wire/src/tests.rs rename to wire/src/tests.rs diff --git a/libs/rust/wire/src/types.rs b/wire/src/types.rs similarity index 100% rename from libs/rust/wire/src/types.rs rename to wire/src/types.rs From 6e7893d6bf86806789adc97603801210e4a0e8c3 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sun, 12 Oct 2025 00:20:50 +0800 Subject: [PATCH 11/46] complete KeystoreService Signed-off-by: qwq233 --- Cargo.toml | 1 + aidl/android/apex/ApexInfo.aidl | 25 ++ aidl/android/apex/IApexService.aidl | 23 ++ build.rs | 1 + src/global.rs | 5 +- src/keymaster/apex.rs | 21 ++ src/keymaster/database/utils.rs | 74 ++++- src/keymaster/db.rs | 384 +++++++++++++++++++++- src/keymaster/keymint_device.rs | 282 ++++++++++++++-- src/keymaster/mod.rs | 2 + src/keymaster/operation.rs | 2 +- src/keymaster/security_level.rs | 17 +- src/keymaster/service.rs | 482 ++++++++++++++++++++++++++++ src/keymaster/super_key.rs | 2 +- src/keymaster/utils.rs | 40 ++- src/lib.rs | 71 ---- src/main.rs | 229 +------------ src/plat/utils.rs | 39 +++ 18 files changed, 1341 insertions(+), 359 deletions(-) create mode 100644 aidl/android/apex/ApexInfo.aidl create mode 100644 aidl/android/apex/IApexService.aidl create mode 100644 src/keymaster/apex.rs create mode 100644 src/keymaster/service.rs delete mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 2b94116..3299c36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ nix = { version = "0.30.1", features = ["mman"] } rsproperties = { version = "0.2.2", features = ["builder"] } libsqlite3-sys = "0.35.0" rand = "0.9.2" +der = "0.7.10" [target.'cfg(target_os = "linux")'.dependencies] rsproperties-service = "*" diff --git a/aidl/android/apex/ApexInfo.aidl b/aidl/android/apex/ApexInfo.aidl new file mode 100644 index 0000000..e7dc306 --- /dev/null +++ b/aidl/android/apex/ApexInfo.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.apex; +parcelable ApexInfo { + @utf8InCpp String moduleName; + @utf8InCpp String modulePath; + @utf8InCpp String preinstalledModulePath; + long versionCode; + @utf8InCpp String versionName; + boolean isFactory; + boolean isActive; +} \ No newline at end of file diff --git a/aidl/android/apex/IApexService.aidl b/aidl/android/apex/IApexService.aidl new file mode 100644 index 0000000..653c747 --- /dev/null +++ b/aidl/android/apex/IApexService.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.apex; + +import android.apex.ApexInfo; + +interface IApexService { + ApexInfo[] getActivePackages(); + ApexInfo[] getAllPackages(); +} \ No newline at end of file diff --git a/build.rs b/build.rs index f33d1e9..204d81d 100644 --- a/build.rs +++ b/build.rs @@ -32,6 +32,7 @@ fn main() { "aidl/android/system/keystore2", "aidl/android/hardware/security/keymint", "aidl/android/security/metrics", + "aidl/android/apex", ]; for dir in dirs { println!("Processing AIDL files in directory: {}", dir); diff --git a/src/global.rs b/src/global.rs index b825b02..1a0ee4e 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,6 +1,6 @@ use std::{ cell::RefCell, - sync::{Arc, LazyLock, Mutex, Once, RwLock}, + sync::{Arc, LazyLock, Mutex, Once, RwLock, OnceLock}, }; use rsbinder::Strong; @@ -61,6 +61,9 @@ pub static ENFORCEMENTS: LazyLock = LazyLock::new(Default::default pub static SUPER_KEY: LazyLock>> = LazyLock::new(Default::default); +/// DER-encoded module information returned by `getSupplementaryAttestationInfo(Tag.MODULE_HASH)`. +pub static ENCODED_MODULE_INFO: OnceLock> = OnceLock::new(); + /// Timestamp service. static TIME_STAMP_DEVICE: Mutex>> = Mutex::new(None); diff --git a/src/keymaster/apex.rs b/src/keymaster/apex.rs new file mode 100644 index 0000000..68187a1 --- /dev/null +++ b/src/keymaster/apex.rs @@ -0,0 +1,21 @@ +use std::cmp::Ordering; + +use der::{DerOrd, Sequence, asn1::OctetString}; + +#[derive(Sequence, Debug)] +pub struct ApexModuleInfo { + pub package_name: OctetString, + pub version_code: u64, +} + +impl DerOrd for ApexModuleInfo { + // DER mandates "encodings of the component values of a set-of value shall appear in ascending + // order". `der_cmp` serves as a proxy for determining that ordering (though why the `der` crate + // requires this is unclear). Essentially, we just need to compare the `name` lengths, and then + // if those are equal, the `name`s themselves. (No need to consider `version`s since there can't + // be more than one `ModuleInfo` with the same `name` in the set-of `ModuleInfo`s.) We rely on + // `OctetString`'s `der_cmp` to do the aforementioned comparison. + fn der_cmp(&self, other: &Self) -> std::result::Result { + self.package_name.der_cmp(&other.package_name) + } +} \ No newline at end of file diff --git a/src/keymaster/database/utils.rs b/src/keymaster/database/utils.rs index 5d81d20..f60787a 100644 --- a/src/keymaster/database/utils.rs +++ b/src/keymaster/database/utils.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use rusqlite::{types::FromSql, Row, Rows}; -use crate::keymaster::error::KsError; +use crate::{android::system::keystore2::{Domain::Domain, KeyDescriptor::KeyDescriptor}, err, keymaster::{db::{KeyType, KeymasterDb}, error::KsError}}; // Takes Rows as returned by a query call on prepared statement. // Extracts exactly one row with the `row_extractor` and fails if more @@ -43,6 +43,78 @@ where } } +const RESPONSE_SIZE_LIMIT: usize = 358400; // 350KB + +fn estimate_safe_amount_to_return( + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + key_descriptors: &[KeyDescriptor], + response_size_limit: usize, +) -> usize { + let mut count = 0; + let mut bytes: usize = 0; + // Estimate the transaction size to avoid returning more items than what + // could fit in a binder transaction. + for kd in key_descriptors.iter() { + // 4 bytes for the Domain enum + // 8 bytes for the Namespace long. + bytes += 4 + 8; + // Size of the alias string. Includes 4 bytes for length encoding. + if let Some(alias) = &kd.alias { + bytes += 4 + alias.len(); + } + // Size of the blob. Includes 4 bytes for length encoding. + if let Some(blob) = &kd.blob { + bytes += 4 + blob.len(); + } + // The binder transaction size limit is 1M. Empirical measurements show + // that the binder overhead is 60% (to be confirmed). So break after + // 350KB and return a partial list. + if bytes > response_size_limit { + log::warn!( + "{domain:?}:{namespace}: Key descriptors list ({} items after {start_past_alias:?}) \ + may exceed binder size, returning {count} items est. {bytes} bytes", + key_descriptors.len(), + ); + break; + } + count += 1; + } + count +} + +/// List all key aliases for a given domain + namespace. whose alias is greater +/// than start_past_alias (if provided). +pub fn list_key_entries( + db: &mut KeymasterDb, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, +) -> Result> { + // The results from the database will be sorted and unique + let db_key_descriptors: Vec = db + .list_past_alias(domain, namespace, KeyType::Client, start_past_alias) + .context(err!("Trying to list keystore database past alias."))?; + + + let safe_amount_to_return = estimate_safe_amount_to_return( + domain, + namespace, + start_past_alias, + &db_key_descriptors, + RESPONSE_SIZE_LIMIT, + ); + Ok(db_key_descriptors[..safe_amount_to_return].to_vec()) +} + +/// Count all key aliases for a given domain + namespace. +pub fn count_key_entries(db: &mut KeymasterDb, domain: Domain, namespace: i64) -> Result { + let num_keys_in_db = db.count_keys(domain, namespace, KeyType::Client)?; + + Ok(num_keys_in_db as i32) +} + /// This struct is defined to postpone converting rusqlite column value to the /// appropriate key parameter value until we know the corresponding tag value. /// Wraps the column index and a rusqlite row. diff --git a/src/keymaster/db.rs b/src/keymaster/db.rs index 120dd1b..c05194f 100644 --- a/src/keymaster/db.rs +++ b/src/keymaster/db.rs @@ -907,6 +907,60 @@ impl KeymasterDb { Ok(()) } + /// Updates the alias column of the given key id `newid` with the given alias, + /// and atomically, removes the alias, domain, and namespace from another row + /// with the same alias-domain-namespace tuple if such row exits. + /// Returns Ok(true) if an old key was marked unreferenced as a hint to the garbage + /// collector. + fn rebind_alias( + tx: &Transaction, + newid: &KeyIdGuard, + alias: &str, + domain: &Domain, + namespace: &i64, + key_type: KeyType, + ) -> Result { + match *domain { + Domain::APP | Domain::SELINUX => {} + _ => { + return Err(KsError::sys()) + .context(err!("Domain {:?} must be either App or SELinux.", domain)); + } + } + let updated = tx + .execute( + "UPDATE persistent.keyentry + SET alias = NULL, domain = NULL, namespace = NULL, state = ? + WHERE alias = ? AND domain = ? AND namespace = ? AND key_type = ?;", + params![KeyLifeCycle::Unreferenced, alias, domain.0 as u32, namespace, key_type], + ) + .context(err!("Failed to rebind existing entry."))?; + let result = tx + .execute( + "UPDATE persistent.keyentry + SET alias = ?, state = ? + WHERE id = ? AND domain = ? AND namespace = ? AND state = ? AND key_type = ?;", + params![ + alias, + KeyLifeCycle::Live, + newid.0, + domain.0 as u32, + *namespace, + KeyLifeCycle::Existing, + key_type, + ], + ) + .context(err!("Failed to set alias."))?; + if result != 1 { + return Err(KsError::sys()).context(err!( + "Expected to update a single entry but instead updated {}.", + result + )); + } + Ok(updated != 0) + } + + /// Store a new key in a single transaction. /// The function creates a new key entry, populates the blob, key parameter, and metadata /// fields, and rebinds the given alias to the new key. @@ -990,6 +1044,56 @@ impl KeymasterDb { .context(err!()) } + /// Store a new certificate + /// The function creates a new key entry, populates the blob field and metadata, and rebinds + /// the given alias to the new cert. + pub fn store_new_certificate( + &mut self, + key: &KeyDescriptor, + key_type: KeyType, + cert: &[u8], + km_uuid: &Uuid, + ) -> Result { + let _wp = wd::watch("KeystoreDB::store_new_certificate"); + + let (alias, domain, namespace) = match key { + KeyDescriptor { alias: Some(alias), domain: Domain::APP, nspace, blob: None } + | KeyDescriptor { alias: Some(alias), domain: Domain::SELINUX, nspace, blob: None } => { + (alias, key.domain, nspace) + } + _ => { + return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) + .context(err!("Need alias and domain must be APP or SELINUX.")); + } + }; + self.with_transaction(Immediate("TX_store_new_certificate"), |tx| { + let key_id = Self::create_key_entry_internal(tx, &domain, namespace, key_type, km_uuid) + .context("Trying to create new key entry.")?; + + Self::set_blob_internal( + tx, + key_id.id(), + SubComponentType::CERT_CHAIN, + Some(cert), + None, + ) + .context("Trying to insert certificate.")?; + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate( + DateTime::now().context("Trying to make creation time.")?, + )); + + metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?; + + let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type) + .context("Trying to rebind alias.")?; + Ok(key_id) + }) + .context(err!()) + } + + /// Loads super key of a given user, if exists pub fn load_super_key( &mut self, @@ -1307,6 +1411,272 @@ impl KeymasterDb { ) } + /// Returns a list of KeyDescriptors in the selected domain/namespace whose + /// aliases are greater than the specified 'start_past_alias'. If no value + /// is provided, returns all KeyDescriptors. + /// The key descriptors will have the domain, nspace, and alias field set. + /// The returned list will be sorted by alias. + /// Domain must be APP or SELINUX, the caller must make sure of that. + /// Number of returned values is limited to 10,000 (which is empirically roughly + /// what will fit in a Binder message). + pub fn list_past_alias( + &mut self, + domain: Domain, + namespace: i64, + key_type: KeyType, + start_past_alias: Option<&str>, + ) -> Result> { + let _wp = wd::watch("KeystoreDB::list_past_alias"); + + let query = format!( + "SELECT DISTINCT alias FROM persistent.keyentry + WHERE domain = ? + AND namespace = ? + AND alias IS NOT NULL + AND state = ? + AND key_type = ? + {} + ORDER BY alias ASC + LIMIT 10000;", + if start_past_alias.is_some() { " AND alias > ?" } else { "" } + ); + + self.with_transaction(TransactionBehavior::Deferred, |tx| { + let mut stmt = tx.prepare(&query).context(err!("Failed to prepare."))?; + + let mut rows = match start_past_alias { + Some(past_alias) => stmt + .query(params![ + domain.0 as u32, + namespace, + KeyLifeCycle::Live, + key_type, + past_alias + ]) + .context(err!("Failed to query."))?, + None => stmt + .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type,]) + .context(err!("Failed to query."))?, + }; + + let mut descriptors: Vec = Vec::new(); + utils::with_rows_extract_all(&mut rows, |row| { + descriptors.push(KeyDescriptor { + domain, + nspace: namespace, + alias: Some(row.get(0).context("Trying to extract alias.")?), + blob: None, + }); + Ok(()) + }) + .context(err!("Failed to extract rows."))?; + Ok(descriptors) + }) + } + + + /// Returns a number of KeyDescriptors in the selected domain/namespace. + /// Domain must be APP or SELINUX, the caller must make sure of that. + pub fn count_keys( + &mut self, + domain: Domain, + namespace: i64, + key_type: KeyType, + ) -> Result { + let _wp = wd::watch("KeystoreDB::countKeys"); + + let num_keys = self.with_transaction(TransactionBehavior::Deferred, |tx| { + tx.query_row( + "SELECT COUNT(alias) FROM persistent.keyentry + WHERE domain = ? + AND namespace = ? + AND alias IS NOT NULL + AND state = ? + AND key_type = ?;", + params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type], + |row| row.get(0), + ) + .context(err!("Failed to count number of keys.")) + })?; + Ok(num_keys) + } + + + /// Adds a grant to the grant table. + /// Like `load_key_entry` this function loads the access tuple before + /// it uses the callback for a permission check. Upon success, + /// it inserts the `grantee_uid`, `key_id`, and `access_vector` into the + /// grant table. The new row will have a randomized id, which is used as + /// grant id in the namespace field of the resulting KeyDescriptor. + pub fn grant( + &mut self, + key: &KeyDescriptor, + caller_uid: u32, + grantee_uid: u32, + access_vector: KeyPermSet, + ) -> Result { + let _wp = wd::watch("KeystoreDB::grant"); + + self.with_transaction(Immediate("TX_grant"), |tx| { + // Load the key_id and complete the access control tuple. + // We ignore the access vector here because grants cannot be granted. + // The access vector returned here expresses the permissions the + // grantee has if key.domain == Domain::GRANT. But this vector + // cannot include the grant permission by design, so there is no way the + // subsequent permission check can pass. + // We could check key.domain == Domain::GRANT and fail early. + // But even if we load the access tuple by grant here, the permission + // check denies the attempt to create a grant by grant descriptor. + let access = + Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(err!())?; + + // Perform access control. It is vital that we return here if the permission + // was denied. So do not touch that '?' at the end of the line. + // This permission check checks if the caller has the grant permission + // for the given key and in addition to all of the permissions + // expressed in `access_vector`. + // check_permission(&access.descriptor, &access_vector) + // .context(err!("check_permission failed"))?; + + let grant_id = if let Some(grant_id) = tx + .query_row( + "SELECT id FROM persistent.grant + WHERE keyentryid = ? AND grantee = ?;", + params![access.key_id, grantee_uid], + |row| row.get(0), + ) + .optional() + .context(err!("Failed get optional existing grant id."))? + { + tx.execute( + "UPDATE persistent.grant + SET access_vector = ? + WHERE id = ?;", + params![i32::from(access_vector), grant_id], + ) + .context(err!("Failed to update existing grant."))?; + grant_id + } else { + Self::insert_with_retry(|id| { + tx.execute( + "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector) + VALUES (?, ?, ?, ?);", + params![id, grantee_uid, access.key_id, i32::from(access_vector)], + ) + }) + .context(err!())? + }; + + Ok(KeyDescriptor { domain: Domain::GRANT, nspace: grant_id, alias: None, blob: None }) + }) + } + + /// This function checks permissions like `grant` and `load_key_entry` + /// before removing a grant from the grant table. + pub fn ungrant( + &mut self, + key: &KeyDescriptor, + caller_uid: u32, + grantee_uid: u32, + ) -> Result<()> { + let _wp = wd::watch("KeystoreDB::ungrant"); + + self.with_transaction(Immediate("TX_ungrant"), |tx| { + // Load the key_id and complete the access control tuple. + // We ignore the access vector here because grants cannot be granted. + let access = + Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(err!())?; + + // Perform access control. We must return here if the permission + // was denied. So do not touch the '?' at the end of this line. + // check_permission(&access.descriptor).context(err!("check_permission failed."))?; + + tx.execute( + "DELETE FROM persistent.grant + WHERE keyentryid = ? AND grantee = ?;", + params![access.key_id, grantee_uid], + ) + .context("Failed to delete grant.")?; + + Ok(()) + }) + } + + /// Marks the given key as unreferenced and removes all of the grants to this key. + /// Returns Ok(true) if a key was marked unreferenced as a hint for the garbage collector. + pub fn unbind_key( + &mut self, + key: &KeyDescriptor, + key_type: KeyType, + caller_uid: u32, + ) -> Result<()> { + let _wp = wd::watch("KeystoreDB::unbind_key"); + + let _result = self.with_transaction(Immediate("TX_unbind_key"), |tx| { + let access = Self::load_access_tuple(tx, key, key_type, caller_uid) + .context("Trying to get access tuple.")?; + + // Perform access control. It is vital that we return here if the permission is denied. + // So do not touch that '?' at the end. + // check_permission(&access.descriptor, access.vector) + // .context("While checking permission.")?; + + Self::mark_unreferenced(tx, access.key_id) + .context("Trying to mark the key unreferenced.") + }) + .context(err!())?; + + Ok(()) + } + + /// Delete all artifacts belonging to the namespace given by the domain-namespace tuple. + /// This leaves all of the blob entries orphaned for subsequent garbage collection. + pub fn unbind_keys_for_namespace(&mut self, domain: Domain, namespace: i64) -> Result<()> { + let _wp = wd::watch("KeystoreDB::unbind_keys_for_namespace"); + + if !(domain == Domain::APP || domain == Domain::SELINUX) { + return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)).context(err!()); + } + self.with_transaction(Immediate("TX_unbind_keys_for_namespace"), |tx| { + tx.execute( + "DELETE FROM persistent.keymetadata + WHERE keyentryid IN ( + SELECT id FROM persistent.keyentry + WHERE domain = ? AND namespace = ? AND key_type = ? + );", + params![domain.0, namespace, KeyType::Client], + ) + .context("Trying to delete keymetadata.")?; + tx.execute( + "DELETE FROM persistent.keyparameter + WHERE keyentryid IN ( + SELECT id FROM persistent.keyentry + WHERE domain = ? AND namespace = ? AND key_type = ? + );", + params![domain.0, namespace, KeyType::Client], + ) + .context("Trying to delete keyparameters.")?; + tx.execute( + "DELETE FROM persistent.grant + WHERE keyentryid IN ( + SELECT id FROM persistent.keyentry + WHERE domain = ? AND namespace = ? AND key_type = ? + );", + params![domain.0, namespace, KeyType::Client], + ) + .context("Trying to delete grants.")?; + tx.execute( + "DELETE FROM persistent.keyentry + WHERE domain = ? AND namespace = ? AND key_type = ?;", + params![domain.0, namespace, KeyType::Client], + ) + .context("Trying to delete keyentry.")?; + Ok(()) + }) + .context(err!()) + } + + /// Fetches a storage statistics atom for a given storage type. For storage /// types that map to a table, information about the table's storage is /// returned. Requests for storage types that are not DB tables return None. @@ -1716,6 +2086,12 @@ impl BlobMetaData { #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Uuid([u8; 16]); +impl From for Uuid { + fn from(sec_level: SecurityLevel) -> Self { + Self((sec_level.0 as u128).to_be_bytes()) + } +} + impl Deref for Uuid { type Target = [u8; 16]; @@ -1725,7 +2101,7 @@ impl Deref for Uuid { } impl ToSql for Uuid { - fn to_sql(&self) -> rusqlite::Result { + fn to_sql(&self) -> rusqlite::Result> { self.0.to_sql() } } @@ -1824,7 +2200,7 @@ impl DateTime { } impl ToSql for DateTime { - fn to_sql(&self) -> rusqlite::Result { + fn to_sql(&self) -> rusqlite::Result> { Ok(ToSqlOutput::Owned(Value::Integer(self.0))) } } @@ -1891,7 +2267,7 @@ enum BlobState { } impl ToSql for BlobState { - fn to_sql(&self) -> rusqlite::Result { + fn to_sql(&self) -> rusqlite::Result> { match self { Self::Current => Ok(ToSqlOutput::Owned(Value::Integer(0))), Self::Superseded => Ok(ToSqlOutput::Owned(Value::Integer(1))), @@ -2149,7 +2525,7 @@ impl SubComponentType { } impl ToSql for SubComponentType { - fn to_sql(&self) -> rusqlite::Result { + fn to_sql(&self) -> rusqlite::Result> { self.0.to_sql() } } diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index d2731eb..1ed6c89 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -29,7 +29,6 @@ use crate::android::system::keystore2::{ use crate::global::AID_KEYSTORE; use crate::keymaster::db::Uuid; use crate::keymaster::error::{map_binder_status, map_ks_error, map_ks_result}; -use crate::keymaster::key_parameter; use crate::keymaster::utils::{key_creation_result_to_aidl, key_param_to_aidl}; use crate::keymint::{clock, sdd, soft}; use crate::{ @@ -57,7 +56,6 @@ use kmr_wire::keymint::{AttestationKey, KeyParam}; use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; use kmr_wire::*; use log::error; -use rand::rand_core::le; use rsbinder::{ExceptionCode, Interface, Status, Strong}; /// Wrapper for operating directly on a KeyMint device. @@ -205,7 +203,7 @@ impl KeyMintDevice { key_type: KeyType, params: &[KeyParameter], validate_characteristics: F, - ) -> Result<(KeyIdGuard, KeyBlob)> + ) -> Result<(KeyIdGuard, KeyBlob<'_>)> where F: FnOnce(&[KeyCharacteristics]) -> bool, { @@ -597,7 +595,7 @@ impl IKeyMintDevice for KeyMintWrapper { )) } }; - + let resp = key_creation_result_to_aidl(result)?; Result::Ok(resp) @@ -614,8 +612,11 @@ impl IKeyMintDevice for KeyMintWrapper { ) -> rsbinder::status::Result< crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, > { - let unwrapping_params: Result> = - unwrapping_params.iter().cloned().map(|p| p.to_km()).collect(); + let unwrapping_params: Result> = unwrapping_params + .iter() + .cloned() + .map(|p| p.to_km()) + .collect(); let unwrapping_params = unwrapping_params .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) .map_err(map_ks_error)?; @@ -635,9 +636,9 @@ impl IKeyMintDevice for KeyMintWrapper { if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } - + let result = match result.rsp.unwrap() { - PerformOpRsp::DeviceImportWrappedKey(rsp) => rsp.ret, + PerformOpRsp::DeviceImportWrappedKey(rsp) => rsp.ret, _ => { return Err(Status::new_service_specific_error( ErrorCode::UNKNOWN_ERROR.0, @@ -652,73 +653,281 @@ impl IKeyMintDevice for KeyMintWrapper { fn upgradeKey( &self, - _arg_keyBlobToUpgrade: &[u8], - _arg_upgradeParams: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + key_blob_to_upgrade: &[u8], + upgrade_params: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], ) -> rsbinder::status::Result> { - todo!() + let upgrade_params: Result> = + upgrade_params.iter().cloned().map(|p| p.to_km()).collect(); + let upgrade_params = upgrade_params + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(map_ks_error)?; + + let req = PerformOpReq::DeviceUpgradeKey(UpgradeKeyRequest { + key_blob_to_upgrade: key_blob_to_upgrade.to_vec(), + upgrade_params, + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + let result = match result.rsp.unwrap() { + PerformOpRsp::DeviceUpgradeKey(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + Result::Ok(result) } - fn deleteKey(&self, _arg_keyBlob: &[u8]) -> rsbinder::status::Result<()> { - todo!() + fn deleteKey(&self, key_blob: &[u8]) -> rsbinder::status::Result<()> { + let key_blob = key_blob.to_vec(); + let req = PerformOpReq::DeviceDeleteKey(DeleteKeyRequest { key_blob }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if result.error_code != 0 { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + Result::Ok(()) } fn deleteAllKeys(&self) -> rsbinder::status::Result<()> { - todo!() + let req = PerformOpReq::DeviceDeleteAllKeys(DeleteAllKeysRequest {}); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if result.error_code != 0 { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + Result::Ok(()) } fn destroyAttestationIds(&self) -> rsbinder::status::Result<()> { - todo!() + let req = PerformOpReq::DeviceDestroyAttestationIds(DestroyAttestationIdsRequest {}); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if result.error_code != 0 { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + Result::Ok(()) } fn deviceLocked( &self, - _arg_passwordOnly: bool, - _arg_timestampToken: Option< + _password_only: bool, + _timestamp_token: Option< &crate::android::hardware::security::secureclock::TimeStampToken::TimeStampToken, >, ) -> rsbinder::status::Result<()> { - todo!() + Result::Err(Status::from(ExceptionCode::UnsupportedOperation)) } fn earlyBootEnded(&self) -> rsbinder::status::Result<()> { - todo!() + let req = PerformOpReq::DeviceEarlyBootEnded(EarlyBootEndedRequest {}); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if result.error_code != 0 { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + Result::Ok(()) } fn convertStorageKeyToEphemeral( &self, - _arg_storageKeyBlob: &[u8], + storage_key_blob: &[u8], ) -> rsbinder::status::Result> { - todo!() + let req = + PerformOpReq::DeviceConvertStorageKeyToEphemeral(ConvertStorageKeyToEphemeralRequest { + storage_key_blob: storage_key_blob.to_vec(), + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + let result = match result.rsp.unwrap() { + PerformOpRsp::DeviceConvertStorageKeyToEphemeral(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + Result::Ok(result) } fn getKeyCharacteristics( &self, - _arg_keyBlob: &[u8], - _arg_appId: &[u8], - _arg_appData: &[u8], + key_blob: &[u8], + app_id: &[u8], + app_data: &[u8], ) -> rsbinder::status::Result< Vec, > { - todo!() + let req = PerformOpReq::DeviceGetKeyCharacteristics(GetKeyCharacteristicsRequest { + key_blob: key_blob.to_vec(), + app_id: app_id.to_vec(), + app_data: app_data.to_vec(), + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + let result = match result.rsp.unwrap() { + PerformOpRsp::DeviceGetKeyCharacteristics(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + let result: Result, rsbinder::Status> = result.iter().map(|kc| { + let params: Result, rsbinder::Status> = kc.authorizations.iter().map(|p| { + key_param_to_aidl(p.clone()) + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(|e| map_ks_error(e)) + }).collect(); + let params = params?; + + Result::Ok(crate::android::hardware::security::keymint::KeyCharacteristics::KeyCharacteristics { + authorizations: params, + securityLevel: match kc.security_level { + kmr_wire::keymint::SecurityLevel::Software => SecurityLevel::SOFTWARE, + kmr_wire::keymint::SecurityLevel::TrustedEnvironment => { + SecurityLevel::TRUSTED_ENVIRONMENT + } + kmr_wire::keymint::SecurityLevel::Strongbox => SecurityLevel::STRONGBOX, + _ => { + return Err(rsbinder::Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }, + }) + + }).collect(); + let result = result?; + + Result::Ok(result) } fn getRootOfTrustChallenge(&self) -> rsbinder::status::Result<[u8; 16]> { - todo!() + let req = PerformOpReq::GetRootOfTrustChallenge(GetRootOfTrustChallengeRequest {}); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + let result = match result.rsp.unwrap() { + PerformOpRsp::GetRootOfTrustChallenge(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + Result::Ok(result) } - fn getRootOfTrust(&self, _arg_challenge: &[u8; 16]) -> rsbinder::status::Result> { - todo!() + fn getRootOfTrust(&self, challenge: &[u8; 16]) -> rsbinder::status::Result> { + let req = PerformOpReq::GetRootOfTrust(GetRootOfTrustRequest { + challenge: *challenge, + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if let None = result.rsp { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + let result = match result.rsp.unwrap() { + PerformOpRsp::GetRootOfTrust(rsp) => rsp.ret, + _ => { + return Err(Status::new_service_specific_error( + ErrorCode::UNKNOWN_ERROR.0, + None, + )) + } + }; + + Result::Ok(result) } - fn sendRootOfTrust(&self, _arg_rootOfTrust: &[u8]) -> rsbinder::status::Result<()> { - todo!() + fn sendRootOfTrust(&self, root_of_trust: &[u8]) -> rsbinder::status::Result<()> { + let req = PerformOpReq::SendRootOfTrust(SendRootOfTrustRequest { + root_of_trust: root_of_trust.to_vec(), + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if result.error_code != 0 { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + Result::Ok(()) } fn setAdditionalAttestationInfo( &self, - _arg_info: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], + info: &[crate::android::hardware::security::keymint::KeyParameter::KeyParameter], ) -> rsbinder::status::Result<()> { - todo!() + let additional_info: Result> = + info.iter().cloned().map(|p| p.to_km()).collect(); + let additional_info = additional_info + .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) + .map_err(map_ks_error)?; + + let req = PerformOpReq::SetAdditionalAttestationInfo(SetAdditionalAttestationInfoRequest { + info: additional_info, + }); + + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if result.error_code != 0 { + return Err(Status::new_service_specific_error(result.error_code, None)); + } + + Result::Ok(()) } } @@ -729,6 +938,15 @@ impl KeyMintWrapper { }) } + pub fn get_hardware_info( + &self, + ) -> Result { + get_keymint_device(self.security_level) + .unwrap() + .get_hardware_info() + .map_err(|_| Error::Km(ErrorCode::UNKNOWN_ERROR)) + } + pub fn op_update_aad( &self, op_handle: i64, diff --git a/src/keymaster/mod.rs b/src/keymaster/mod.rs index 9f60db1..824d053 100644 --- a/src/keymaster/mod.rs +++ b/src/keymaster/mod.rs @@ -1,3 +1,4 @@ +pub mod apex; pub mod attestation_key_utils; pub mod boot_key; pub mod crypto; @@ -13,4 +14,5 @@ pub mod operation; pub mod permission; pub mod security_level; pub mod super_key; +pub mod service; pub mod utils; diff --git a/src/keymaster/operation.rs b/src/keymaster/operation.rs index f9ff127..35e7405 100644 --- a/src/keymaster/operation.rs +++ b/src/keymaster/operation.rs @@ -328,7 +328,7 @@ impl Operation { // the locked outcome for further updates. In any other case it returns // ErrorCode::INVALID_OPERATION_HANDLE indicating that this operation has // been finalized and is no longer active. - fn check_active(&self) -> Result> { + fn check_active(&self) -> Result> { let guard = self.outcome.lock().expect("In check_active."); match *guard { Outcome::Unknown => Ok(guard), diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 8dbe767..7052aeb 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -41,7 +41,7 @@ use crate::keymaster::key_parameter::KeyParameterValue as KsKeyParamValue; use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; -use kmr_ta::HardwareInfo; +use kmr_wire::keymint::KeyMintHardwareInfo; use log::debug; use rsbinder::{thread_state::CallingContext, Interface, Status}; @@ -50,19 +50,26 @@ static ZERO_BLOB_32: &[u8] = &[0; 32]; pub struct KeystoreSecurityLevel { security_level: SecurityLevel, - hw_info: HardwareInfo, + hw_info: KeyMintHardwareInfo, km_uuid: Uuid, operation_db: OperationDb, } impl KeystoreSecurityLevel { - pub fn new(security_level: SecurityLevel, hw_info: HardwareInfo, km_uuid: Uuid) -> Self { - KeystoreSecurityLevel { + pub fn new(security_level: SecurityLevel) -> Result<(Self, Uuid)> { + let km_uuid = Uuid::from(security_level); + + let hw_info = get_keymint_wrapper(security_level) + .unwrap() + .get_hardware_info() + .context(err!("Failed to get hardware info."))?; + + Ok((KeystoreSecurityLevel { security_level, hw_info, km_uuid, operation_db: OperationDb::new(), - } + }, km_uuid)) } fn watch_millis(&self, id: &'static str, millis: u64) -> Option { diff --git a/src/keymaster/service.rs b/src/keymaster/service.rs new file mode 100644 index 0000000..6c064cb --- /dev/null +++ b/src/keymaster/service.rs @@ -0,0 +1,482 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This crate implement the core Keystore 2.0 service API as defined by the Keystore 2.0 +//! AIDL spec. + +use std::collections::HashMap; + +use crate::android::hardware::security::keymint::ErrorCode::ErrorCode; +use crate::android::system::keystore2::IKeystoreSecurityLevel::BnKeystoreSecurityLevel; +use crate::android::system::keystore2::ResponseCode::ResponseCode; +use crate::err; +use crate::global::ENCODED_MODULE_INFO; +use crate::keymaster::apex::ApexModuleInfo; +use crate::keymaster::database::utils::{count_key_entries, list_key_entries}; +use crate::keymaster::db::KEYSTORE_UUID; +use crate::keymaster::db::{KeyEntryLoadBits, KeyType, SubComponentType}; +use crate::keymaster::error::{into_logged_binder, KsError as Error}; +use crate::keymaster::permission::KeyPermSet; +use crate::keymaster::security_level::KeystoreSecurityLevel; +use crate::keymaster::utils::key_parameters_to_authorizations; +use crate::plat::utils::multiuser_get_user_id; +use crate::watchdog as wd; +use crate::{ + global::{DB, SUPER_KEY}, + keymaster::db::Uuid, +}; + +use crate::android::hardware::security::keymint::SecurityLevel::SecurityLevel; +use crate::android::hardware::security::keymint::Tag::Tag; +use crate::android::system::keystore2::{ + Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, + IKeystoreService::BnKeystoreService, IKeystoreService::IKeystoreService, + KeyDescriptor::KeyDescriptor, KeyEntryResponse::KeyEntryResponse, KeyMetadata::KeyMetadata, +}; +use anyhow::{Context, Ok, Result}; +use der::Encode; +use der::asn1::SetOfVec; +use kmr_crypto_boring::sha256::BoringSha256; +use kmr_common::crypto::Sha256; +use log::debug; +use rsbinder::thread_state::CallingContext; +use rsbinder::{Status, Strong}; + + +fn encode_module_info(module_info: Vec) -> Result, der::Error> { + SetOfVec::::from_iter(module_info.into_iter())?.to_der() +} + +/// Implementation of the IKeystoreService. +#[derive(Default)] +pub struct KeystoreService { + i_sec_level_by_uuid: HashMap>, + uuid_by_sec_level: HashMap, +} + +impl KeystoreService { + /// Create a new instance of the Keystore 2.0 service. + pub fn new_native_binder( + ) -> Result> { + let mut result: Self = Default::default(); + + let (dev, uuid) = match KeystoreSecurityLevel::new( + SecurityLevel::TRUSTED_ENVIRONMENT, + ) { + Result::Ok(v) => v, + Err(e) => { + log::error!("Failed to construct mandatory security level TEE: {e:?}"); + log::error!("Does the device have a /default Keymaster or KeyMint instance?"); + return Err(e.context(err!("Trying to construct mandatory security level TEE"))); + } + }; + + let dev: Strong = BnKeystoreSecurityLevel::new_binder(dev); + + result.i_sec_level_by_uuid.insert(uuid, dev); + result + .uuid_by_sec_level + .insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid); + + // Strongbox is optional, so we ignore errors and turn the result into an Option. + if let Result::Ok((dev, uuid)) = + KeystoreSecurityLevel::new(SecurityLevel::STRONGBOX) + { + let dev: Strong = BnKeystoreSecurityLevel::new_binder(dev); + result.i_sec_level_by_uuid.insert(uuid, dev); + result + .uuid_by_sec_level + .insert(SecurityLevel::STRONGBOX, uuid); + } + + Ok(BnKeystoreService::new_binder( + result + )) + } + + fn uuid_to_sec_level(&self, uuid: &Uuid) -> SecurityLevel { + self.uuid_by_sec_level + .iter() + .find(|(_, v)| **v == *uuid) + .map(|(s, _)| *s) + .unwrap_or(SecurityLevel::SOFTWARE) + } + + fn get_i_sec_level_by_uuid(&self, uuid: &Uuid) -> Result> { + if let Some(dev) = self.i_sec_level_by_uuid.get(uuid) { + Ok(dev.clone()) + } else { + Err(Error::sys()).context(err!("KeyMint instance for key not found.")) + } + } + + fn get_security_level( + &self, + sec_level: SecurityLevel, + ) -> Result> { + if let Some(dev) = self + .uuid_by_sec_level + .get(&sec_level) + .and_then(|uuid| self.i_sec_level_by_uuid.get(uuid)) + { + Ok(dev.clone()) + } else { + Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context(err!("No such security level.")) + } + } + + fn get_key_entry(&self, key: &KeyDescriptor) -> Result { + let caller_uid = CallingContext::default().uid; + + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(multiuser_get_user_id(caller_uid)); + + let (key_id_guard, mut key_entry) = DB + .with(|db| { + db.borrow_mut().load_key_entry( + key, + KeyType::Client, + KeyEntryLoadBits::PUBLIC, + caller_uid, + ) + }) + .context(err!("while trying to load key info."))?; + + let i_sec_level = if !key_entry.pure_cert() { + Some( + self.get_i_sec_level_by_uuid(key_entry.km_uuid()) + .context(err!("Trying to get security level proxy."))?, + ) + } else { + None + }; + + Ok(KeyEntryResponse { + iSecurityLevel: i_sec_level, + metadata: KeyMetadata { + key: KeyDescriptor { + domain: Domain::KEY_ID, + nspace: key_id_guard.id(), + ..Default::default() + }, + keySecurityLevel: self.uuid_to_sec_level(key_entry.km_uuid()), + certificate: key_entry.take_cert(), + certificateChain: key_entry.take_cert_chain(), + modificationTimeMs: key_entry + .metadata() + .creation_date() + .map(|d| d.to_millis_epoch()) + .ok_or(Error::Rc(ResponseCode::VALUE_CORRUPTED)) + .context(err!("Trying to get creation date."))?, + authorizations: key_parameters_to_authorizations(key_entry.into_key_parameters()), + }, + }) + } + + fn update_subcomponent( + &self, + key: &KeyDescriptor, + public_cert: Option<&[u8]>, + certificate_chain: Option<&[u8]>, + ) -> Result<()> { + let caller_uid = CallingContext::default().uid; + let _super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(multiuser_get_user_id(caller_uid)); + + DB.with::<_, Result<()>>(|db| { + let entry = match db.borrow_mut().load_key_entry( + key, + KeyType::Client, + KeyEntryLoadBits::NONE, + caller_uid, + ) { + Err(e) => match e.root_cause().downcast_ref::() { + Some(Error::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None), + _ => Err(e), + }, + Result::Ok(v) => Ok(Some(v)), + }?; + + let mut db = db.borrow_mut(); + if let Some((key_id_guard, _key_entry)) = entry { + db.set_blob(&key_id_guard, SubComponentType::CERT, public_cert, None) + .context(err!("Failed to update cert subcomponent."))?; + + db.set_blob( + &key_id_guard, + SubComponentType::CERT_CHAIN, + certificate_chain, + None, + ) + .context(err!("Failed to update cert chain subcomponent."))?; + return Ok(()); + } + + // If we reach this point we have to check the special condition where a certificate + // entry may be made. + if !(public_cert.is_none() && certificate_chain.is_some()) { + return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)) + .context(err!("No key to update.")); + } + + // So we know that we have a certificate chain and no public cert. + // Now check that we have everything we need to make a new certificate entry. + let key = match (key.domain, &key.alias) { + (Domain::APP, Some(ref alias)) => KeyDescriptor { + domain: Domain::APP, + nspace: CallingContext::default().uid as i64, + alias: Some(alias.clone()), + blob: None, + }, + (Domain::SELINUX, Some(_)) => key.clone(), + _ => { + return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(err!( + "Domain must be APP or SELINUX to insert a certificate." + )) + } + }; + + // Security critical: This must return on failure. Do not remove the `?`; + // check_key_permission(KeyPerm::Rebind, &key, &None) + // .context(err!("Caller does not have permission to insert this certificate."))?; + + db.store_new_certificate( + &key, + KeyType::Client, + certificate_chain.unwrap(), + &KEYSTORE_UUID, + ) + .context(err!("Failed to insert new certificate."))?; + Ok(()) + }) + .context(err!()) + } + + fn get_key_descriptor_for_lookup( + &self, + domain: Domain, + namespace: i64, + ) -> Result { + let k = match domain { + Domain::APP => KeyDescriptor { + domain, + nspace: CallingContext::default().uid as u64 as i64, + ..Default::default() + }, + Domain::SELINUX => KeyDescriptor { + domain, + nspace: namespace, + ..Default::default() + }, + _ => { + return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(err!( + "List entries is only supported for Domain::APP and Domain::SELINUX." + )) + } + }; + + // First we check if the caller has the info permission for the selected domain/namespace. + // By default we use the calling uid as namespace if domain is Domain::APP. + // If the first check fails we check if the caller has the list permission allowing to list + // any namespace. In that case we also adjust the queried namespace if a specific uid was + // selected. + // if let Err(e) = check_key_permission(KeyPerm::GetInfo, &k, &None) { + // if let Some(selinux::Error::PermissionDenied) = + // e.root_cause().downcast_ref::() + // { + // check_keystore_permission(KeystorePerm::List) + // .context(err!("While checking keystore permission."))?; + // if namespace != -1 { + // k.nspace = namespace; + // } + // } else { + // return Err(e).context(err!("While checking key permission."))?; + // } + // } + Ok(k) + } + + fn list_entries(&self, domain: Domain, namespace: i64) -> Result> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + + DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, None)) + } + + fn count_num_entries(&self, domain: Domain, namespace: i64) -> Result { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + + DB.with(|db| count_key_entries(&mut db.borrow_mut(), k.domain, k.nspace)) + } + + fn get_supplementary_attestation_info(&self, tag: Tag) -> Result> { + match tag { + Tag::MODULE_HASH => { + let info = ENCODED_MODULE_INFO.get_or_try_init(|| -> Result, anyhow::Error> { + let apex_info = crate::plat::utils::get_apex_module_info()?; + + let encoding = encode_module_info(apex_info) + .map_err(|_| anyhow::anyhow!("Failed to encode module info."))?; + + let sha256 = BoringSha256 {}; + + let hash = sha256.hash(&encoding).map_err(|_| anyhow::anyhow!("Failed to hash module info."))?; + + Ok(hash.to_vec()) + })?; + + Ok(info.clone()) + } + _ => Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(err!( + "Tag {tag:?} not supported for getSupplementaryAttestationInfo." + )), + } + } + + fn list_entries_batched( + &self, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + ) -> Result> { + let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, start_past_alias)) + } + + fn delete_key(&self, key: &KeyDescriptor) -> Result<()> { + let caller_uid = CallingContext::default().uid; + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(multiuser_get_user_id(caller_uid)); + + DB.with(|db| db.borrow_mut().unbind_key(key, KeyType::Client, caller_uid)) + .context(err!("Trying to unbind the key."))?; + Ok(()) + } + + fn grant( + &self, + key: &KeyDescriptor, + grantee_uid: i32, + access_vector: KeyPermSet, + ) -> Result { + let caller_uid = CallingContext::default().uid; + let super_key = SUPER_KEY + .read() + .unwrap() + .get_after_first_unlock_key_by_user_id(multiuser_get_user_id(caller_uid)); + + DB.with(|db| { + db.borrow_mut() + .grant(key, caller_uid, grantee_uid as u32, access_vector) + }) + .context(err!("KeystoreService::grant.")) + } + + fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<()> { + DB.with(|db| { + db.borrow_mut() + .ungrant(key, CallingContext::default().uid, grantee_uid as u32) + }) + .context(err!("KeystoreService::ungrant.")) + } +} + +impl rsbinder::Interface for KeystoreService {} + +// Implementation of IKeystoreService. See AIDL spec at +// system/security/keystore2/binder/android/security/keystore2/IKeystoreService.aidl +impl IKeystoreService for KeystoreService { + fn getSecurityLevel( + &self, + security_level: SecurityLevel, + ) -> Result, Status> { + let _wp = wd::watch_millis_with("IKeystoreService::getSecurityLevel", 500, security_level); + self.get_security_level(security_level) + .map_err(into_logged_binder) + } + fn getKeyEntry(&self, key: &KeyDescriptor) -> Result { + let _wp = wd::watch("IKeystoreService::get_key_entry"); + self.get_key_entry(key).map_err(into_logged_binder) + } + fn updateSubcomponent( + &self, + key: &KeyDescriptor, + public_cert: Option<&[u8]>, + certificate_chain: Option<&[u8]>, + ) -> Result<(), Status> { + let _wp = wd::watch("IKeystoreService::updateSubcomponent"); + self.update_subcomponent(key, public_cert, certificate_chain) + .map_err(into_logged_binder) + } + fn listEntries(&self, domain: Domain, namespace: i64) -> Result, Status> { + let _wp = wd::watch("IKeystoreService::listEntries"); + self.list_entries(domain, namespace) + .map_err(into_logged_binder) + } + fn deleteKey(&self, key: &KeyDescriptor) -> Result<(), Status> { + let _wp = wd::watch("IKeystoreService::deleteKey"); + let result = self.delete_key(key); + debug!( + "deleteKey: key={:?}, uid={}", + key, + CallingContext::default().uid + ); + result.map_err(into_logged_binder) + } + fn grant( + &self, + key: &KeyDescriptor, + grantee_uid: i32, + access_vector: i32, + ) -> Result { + let _wp = wd::watch("IKeystoreService::grant"); + self.grant(key, grantee_uid, access_vector.into()) + .map_err(into_logged_binder) + } + fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<(), Status> { + let _wp = wd::watch("IKeystoreService::ungrant"); + self.ungrant(key, grantee_uid).map_err(into_logged_binder) + } + fn listEntriesBatched( + &self, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + ) -> Result, Status> { + let _wp = wd::watch("IKeystoreService::listEntriesBatched"); + self.list_entries_batched(domain, namespace, start_past_alias) + .map_err(into_logged_binder) + } + + fn getNumberOfEntries(&self, domain: Domain, namespace: i64) -> Result { + let _wp = wd::watch("IKeystoreService::getNumberOfEntries"); + self.count_num_entries(domain, namespace) + .map_err(into_logged_binder) + } + + fn getSupplementaryAttestationInfo(&self, tag: Tag) -> Result, Status> { + let _wp = wd::watch("IKeystoreService::getSupplementaryAttestationInfo"); + self.get_supplementary_attestation_info(tag).map_err(|e| { + log::error!("Failed to get supplementary attestation info: {}", e); + // pretend as it's not supported + Status::from(rsbinder::StatusCode::UnknownTransaction) + }) + } +} diff --git a/src/keymaster/super_key.rs b/src/keymaster/super_key.rs index 75757ce..b74b576 100644 --- a/src/keymaster/super_key.rs +++ b/src/keymaster/super_key.rs @@ -38,7 +38,7 @@ use crate::{ }, plat::property_watcher::PropertyWatcher, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use kmr_common::crypto::AES_256_KEY_LENGTH; use kmr_crypto_boring::km::*; use kmr_crypto_boring::zvec::ZVec; diff --git a/src/keymaster/utils.rs b/src/keymaster/utils.rs index 3461efe..90d96d7 100644 --- a/src/keymaster/utils.rs +++ b/src/keymaster/utils.rs @@ -1,19 +1,15 @@ -use std::result; use crate::{ - android::hardware::security::{ - self, - keymint::{ + android::hardware::security::keymint::{ ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, - KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag, + KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, }, - }, consts, err, keymaster::{ - error::{KsError as Error, map_ks_error}, + error::{map_ks_error, KsError as Error}, key_parameter::KeyParameter, - keymint_device::{KeyMintDevice, get_keymint_wrapper}, + keymint_device::{get_keymint_wrapper, KeyMintDevice}, }, watchdog, }; @@ -609,11 +605,12 @@ impl crate::android::hardware::security::keymint::KeyParameter::KeyParameter { pub fn key_creation_result_to_aidl( result: kmr_wire::keymint::KeyCreationResult, -) -> Result { - - let certificates: Vec< - crate::android::hardware::security::keymint::Certificate::Certificate, - > = result +) -> Result< + crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult, + rsbinder::Status, +> { + let certificates: Vec = + result .certificate_chain .iter() .map( @@ -623,7 +620,7 @@ pub fn key_creation_result_to_aidl( ) .collect(); - let key_characteristics: Result, rsbinder::Status> = result.key_characteristics.iter().map(|kc| { + let key_characteristics: Result, rsbinder::Status> = result.key_characteristics.iter().map(|kc| { let params: Result, rsbinder::Status> = kc.authorizations.iter().map(|p| { key_param_to_aidl(p.clone()) .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) @@ -649,16 +646,15 @@ pub fn key_creation_result_to_aidl( }) }).collect(); - let key_characteristics = key_characteristics?; + let key_characteristics = key_characteristics?; - let resp = - crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult { - keyBlob: result.key_blob, - keyCharacteristics: key_characteristics, - certificateChain: certificates, - }; + let resp = crate::android::hardware::security::keymint::KeyCreationResult::KeyCreationResult { + keyBlob: result.key_blob, + keyCharacteristics: key_characteristics, + certificateChain: certificates, + }; - Result::Ok(resp) + Result::Ok(resp) } pub fn key_param_to_aidl( diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 236eb0b..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,71 +0,0 @@ -#![recursion_limit = "256"] -#![feature(once_cell_get_mut)] - -use anyhow::Context; -use lazy_static; -use std::{ - panic, - sync::{Arc, Mutex}, -}; - -use jni::JavaVM; -use log::{debug, error}; - -pub mod consts; -pub mod global; -pub mod keymaster; -pub mod keymint; -pub mod logging; -pub mod macros; -pub mod plat; -pub mod proto; -pub mod utils; -pub mod watchdog; - -include!(concat!(env!("OUT_DIR"), "/aidl.rs")); - -lazy_static::lazy_static! { - static ref JAVA_VM: Arc>> = Arc::new(Mutex::new(None)); -} - -#[no_mangle] -pub extern "C" fn init(_env: jni::JNIEnv, _class: jni::objects::JClass) { - debug!("nativeInit called"); - // You can add more initialization code here if needed -} - -#[no_mangle] -pub extern "C" fn JNI_OnLoad( - vm: *mut jni::sys::JavaVM, - _reserved: *mut std::ffi::c_void, -) -> jni::sys::jint { - let jvm = unsafe { jni::JavaVM::from_raw(vm).expect("Failed to get JavaVM from raw pointer") }; - let mut env = jvm.get_env().unwrap(); - - logging::init_logger(); - rsbinder::ProcessState::init_default(); - debug!("Hello, OhMyKeymint!"); - - // Redirect panic messages to logcat. - panic::set_hook(Box::new(|panic_info| { - error!("{}", panic_info); - })); - - let class = env - .find_class("top/qwq2333/ohmykeymint/Native") - .context("Failed to find class top/qwq2333/ohmykeymint/Native") - .unwrap(); - - let methods = jni_methods![["nativeInit", "()V", init], ["init", "()V", init],]; - - debug!("Registering native methods"); - env.register_native_methods(class, methods.as_slice()) - .context("Failed to register native methods") - .unwrap(); - - debug!("Saving JavaVM instance"); - let mut java_vm_lock = JAVA_VM.lock().unwrap(); - *java_vm_lock = Some(jvm); - - return jni::sys::JNI_VERSION_1_6; -} diff --git a/src/main.rs b/src/main.rs index dff0abe..0f9ee7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,16 @@ #![recursion_limit = "256"] #![feature(once_cell_get_mut)] +#![feature(once_cell_try)] -use std::{io::Read, panic}; +use std::panic; -use kmr_common::crypto::{self, MonotonicClock}; -use kmr_crypto_boring::{ - aes::BoringAes, aes_cmac::BoringAesCmac, des::BoringDes, ec::BoringEc, eq::BoringEq, - hmac::BoringHmac, rng::BoringRng, rsa::BoringRsa, sha256::BoringSha256, -}; -use kmr_ta::{ - device::{CsrSigningAlgorithm, Implementation}, - HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3, -}; -use kmr_wire::{ - keymint::{DateTime, KeyParam, KeyPurpose, SecurityLevel}, - rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR, - GenerateKeyRequest, GetHardwareInfoRequest, KeySizeInBits, PerformOpReq, -}; -use log::{debug, error, info}; -use rsbinder::Parcelable; +use log::{debug, error}; -use crate::{ - android::system::keystore2::{ - CreateOperationResponse::CreateOperationResponse, IKeystoreOperation, KeyDescriptor, - }, - keymint::{attest, clock, sdd, soft}, - utils::ParcelExt, -}; +use crate:: + android::system::keystore2:: + IKeystoreOperation + +; pub mod consts; pub mod global; @@ -58,203 +42,6 @@ fn main() { debug!("Hello, OhMyKeymint!"); - let security_level: SecurityLevel = SecurityLevel::TrustedEnvironment; - let hw_info = HardwareInfo { - version_number: 2, - security_level, - impl_name: "Qualcomm QTEE KeyMint 2", - author_name: "Qualcomm Technologies", - unique_id: "Qualcomm QTEE KeyMint 2", - }; - - let rpc_sign_algo = CsrSigningAlgorithm::EdDSA; - let rpc_info_v3 = RpcInfoV3 { - author_name: "Qualcomm Technologies", - unique_id: "Qualcomm QTEE KeyMint 2", - fused: false, - supported_num_of_keys_in_csr: MINIMUM_SUPPORTED_KEYS_IN_CSR, - }; - - let mut rng = BoringRng; - let sdd_mgr: Option> = - match sdd::HostSddManager::new(&mut rng) { - Ok(v) => Some(Box::new(v)), - Err(e) => { - error!("Failed to initialize secure deletion data manager: {:?}", e); - None - } - }; - let clock = clock::StdClock; - let rsa = BoringRsa::default(); - let ec = BoringEc::default(); - let hkdf: Box = Box::new(BoringHmac); - let imp = crypto::Implementation { - rng: Box::new(rng), - clock: Some(Box::new(clock)), - compare: Box::new(BoringEq), - aes: Box::new(BoringAes), - des: Box::new(BoringDes), - hmac: Box::new(BoringHmac), - rsa: Box::new(rsa), - ec: Box::new(ec), - ckdf: Box::new(BoringAesCmac), - hkdf, - sha256: Box::new(BoringSha256), - }; - - let keys: Box = Box::new(soft::Keys); - let rpc: Box = Box::new(soft::RpcArtifacts::new( - soft::Derive::default(), - rpc_sign_algo, - )); - - let dev = Implementation { - keys, - // Cuttlefish has `remote_provisioning.tee.rkp_only=1` so don't support batch signing - // of keys. This can be reinstated with: - // ``` - // sign_info: Some(kmr_ta_nonsecure::attest::CertSignInfo::new()), - // ``` - sign_info: Some(Box::new(attest::CertSignInfo::new())), - // HAL populates attestation IDs from properties. - attest_ids: None, - sdd_mgr, - // `BOOTLOADER_ONLY` keys not supported. - bootloader: Box::new(kmr_ta::device::BootloaderDone), - // `STORAGE_KEY` keys not supported. - sk_wrapper: None, - // `TRUSTED_USER_PRESENCE_REQUIRED` keys not supported - tup: Box::new(kmr_ta::device::TrustedPresenceUnsupported), - // No support for converting previous implementation's keyblobs. - legacy_key: None, - rpc, - }; - - let mut ta = KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev); - - let req = PerformOpReq::DeviceGetHardwareInfo(GetHardwareInfoRequest {}); - let resp = ta.process_req(req); - debug!("GetHardwareInfo response: {:?}", resp); - - let req = PerformOpReq::SetBootInfo(kmr_wire::SetBootInfoRequest { - verified_boot_state: 0, // Verified - verified_boot_hash: vec![0; 32], - verified_boot_key: vec![0; 32], - device_boot_locked: true, - boot_patchlevel: 20250605, - }); - let resp = ta.process_req(req); - debug!("SetBootInfo response: {:?}", resp); - - let req = PerformOpReq::SetHalInfo(kmr_wire::SetHalInfoRequest { - os_version: 35, - os_patchlevel: 202506, - vendor_patchlevel: 202506, - }); - let resp = ta.process_req(req); - debug!("SetHalInfo response: {:?}", resp); - - let req = PerformOpReq::SetHalVersion(kmr_wire::SetHalVersionRequest { aidl_version: 400 }); - let resp = ta.process_req(req); - debug!("SetHalVersion response: {:?}", resp); - - let req = PerformOpReq::SetAttestationIds(kmr_wire::SetAttestationIdsRequest { - ids: kmr_wire::AttestationIdInfo { - brand: "generic".into(), - device: "generic".into(), - product: "generic".into(), - serial: "0123456789ABCDEF".into(), - manufacturer: "Generic".into(), - model: "GenericModel".into(), - imei: "350505563694821".into(), - imei2: "350505563694822".into(), - meid: "350505563694823".into(), - }, - }); - let resp = ta.process_req(req); - debug!("SetAttestationIds response: {:?}", resp); - - let req = PerformOpReq::DeviceEarlyBootEnded(kmr_wire::EarlyBootEndedRequest {}); - let resp = ta.process_req(req); - debug!("DeviceEarlyBootEnded response: {:?}", resp); - - let clock = clock::StdClock; - let current_time = clock.now().0; - - let req = PerformOpReq::DeviceGenerateKey(GenerateKeyRequest { - key_params: vec![ - KeyParam::ApplicationId("com.example.app".as_bytes().to_vec()), - KeyParam::AttestationApplicationId("com.example.app".as_bytes().to_vec()), - KeyParam::Purpose(KeyPurpose::AttestKey), - KeyParam::KeySize(KeySizeInBits(256)), - KeyParam::Algorithm(kmr_wire::keymint::Algorithm::Ec), - KeyParam::EcCurve(kmr_wire::keymint::EcCurve::P256), - KeyParam::Digest(kmr_wire::keymint::Digest::Sha256), - KeyParam::NoAuthRequired, - KeyParam::CertificateNotBefore(DateTime { - ms_since_epoch: current_time - 10000, - }), // -10 seconds - KeyParam::CertificateNotAfter(DateTime { - ms_since_epoch: current_time + 31536000000, - }), // +1 year - KeyParam::CertificateSerial(b"1234567890".to_vec()), - KeyParam::CertificateSubject(kmr_wire::keymint::DEFAULT_CERT_SUBJECT.to_vec()), - KeyParam::AttestationChallenge(b"Test Attestation Challenge".to_vec()), - ], - attestation_key: None, - }); - let resp = ta.process_req(req); - match &resp.rsp { - Some(rsp) => { - if let kmr_wire::PerformOpRsp::DeviceGenerateKey(ref key_rsp) = rsp { - std::fs::create_dir_all("./omk/output").unwrap(); - std::fs::write( - "./omk/output/cert.der", - key_rsp.ret.certificate_chain[0].encoded_certificate.clone(), - ) - .unwrap(); - } else { - error!("Unexpected response: {:?}", resp); - } - } - None => { - error!("No response received"); - } - } - - let mut key_descriptor = KeyDescriptor::KeyDescriptor::default(); - - let mut bytes: Vec = Vec::new(); - std::fs::read("./parcel.bin") - .unwrap() - .as_slice() - .read_to_end(&mut bytes) - .unwrap(); - let mut parcel = rsbinder::Parcel::from_vec(bytes); - key_descriptor.read_from_parcel(&mut parcel).unwrap(); - - info!("KeyDescriptor from parcel: {:?}", key_descriptor); - - let mut parcel = rsbinder::Parcel::new(); - - let op = KeystoreOperation {}; - let op = IKeystoreOperation::BnKeystoreOperation::new_binder(op); - - let mut create_op_resp = CreateOperationResponse { - iOperation: Some(op), - operationChallenge: None, - parameters: None, - upgradedBlob: None, - }; - - parcel.write(&mut create_op_resp).unwrap(); - - info!("Parcel raw data: {:x?}", parcel.data()); - info!( - "CreateOperationResponse written to parcel, size: {}", - parcel.data_size() - ); - info!("data: {:?}", parcel); } #[derive(Clone)] diff --git a/src/plat/utils.rs b/src/plat/utils.rs index 6bf3c99..82e4d82 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -3,13 +3,17 @@ use std::sync::Arc; use anyhow::Ok; use log::debug; use rsbinder::{hub, DeathRecipient}; +use x509_cert::der::asn1::OctetString; use crate::android::content::pm::IPackageManager::IPackageManager; +use crate::android::apex::IApexService::IApexService; +use crate::keymaster::apex::ApexModuleInfo; use std::cell::RefCell; thread_local! { static PM: RefCell>> = RefCell::new(None); + static APEX: RefCell>> = RefCell::new(None); } struct MyDeathRecipient; @@ -41,6 +45,24 @@ fn get_pm() -> anyhow::Result> { }) } +#[allow(non_snake_case)] +fn get_apex() -> anyhow::Result> { + APEX.with(|p| { + if let Some(iPm) = p.borrow().as_ref() { + Ok(iPm.clone()) + } else { + let pm: rsbinder::Strong = hub::get_interface("apexservice")?; + let recipient = Arc::new(MyDeathRecipient {}); + + pm.as_binder() + .link_to_death(Arc::downgrade(&(recipient as Arc)))?; + + *p.borrow_mut() = Some(pm.clone()); + Ok(pm) + } + }) +} + pub fn get_aaid(uid: u32) -> anyhow::Result { if (uid == 0) || (uid == 1000) { return Ok("android".to_string()); @@ -53,6 +75,23 @@ pub fn get_aaid(uid: u32) -> anyhow::Result { Ok(package_names[0].clone()) } +pub fn get_apex_module_info() -> anyhow::Result> { + let apex = get_apex()?; + let result: Vec = apex.getAllPackages()?; + + let result: Vec = result + .iter() + .map(|i| { + Ok(ApexModuleInfo { + package_name: OctetString::new(i.moduleName.as_bytes())?, + version_code: i.versionCode as u64, + }) + }) + .collect::>>()?; + + Ok(result) +} + pub const AID_USER_OFFSET: u32 = 100000; /// Gets the user id from a uid. From 0748eb25386166d072a2af1972847250b8529fbb Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sun, 12 Oct 2025 02:24:14 +0800 Subject: [PATCH 12/46] fix tag aidl Signed-off-by: qwq233 --- Cargo.toml | 2 +- .../hardware/security/keymint/Tag.aidl | 136 +++++++++--------- src/keymaster/keymint_device.rs | 35 ++++- src/keymaster/security_level.rs | 48 ++++--- src/logging.rs | 2 +- src/main.rs | 20 ++- ta/src/lib.rs | 59 +++----- 7 files changed, 169 insertions(+), 133 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3299c36..d2f9a42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ prost-types = "0.14.1" hex = "0.4.3" log4rs = "1.4.0" log = "0.4.28" -rsbinder = "0.4.2" +rsbinder = { version = "0.4.2", features = ["android_12_plus"] } rusqlite = { version = "0.37.0", features = ["bundled"] } anyhow = "1.0.100" async-trait = "0.1.89" diff --git a/aidl/android/hardware/security/keymint/Tag.aidl b/aidl/android/hardware/security/keymint/Tag.aidl index 7ea5f5d..f9bc36c 100644 --- a/aidl/android/hardware/security/keymint/Tag.aidl +++ b/aidl/android/hardware/security/keymint/Tag.aidl @@ -43,7 +43,7 @@ enum Tag { * * Must be hardware-enforced. */ - PURPOSE = TagType.ENUM_REP | 1, + PURPOSE = 536870913, /** * Tag::ALGORITHM specifies the cryptographic algorithm with which the key is used. This tag @@ -52,7 +52,7 @@ enum Tag { * * Must be hardware-enforced. */ - ALGORITHM = TagType.ENUM | 2, + ALGORITHM = 268435458, /** * Tag::KEY_SIZE specifies the size, in bits, of the key, measuring in the normal way for the @@ -64,7 +64,7 @@ enum Tag { * * Must be hardware-enforced. */ - KEY_SIZE = TagType.UINT | 3, + KEY_SIZE = 805306371, /** * Tag::BLOCK_MODE specifies the block cipher mode(s) with which the key may be used. This tag @@ -77,7 +77,7 @@ enum Tag { * * Must be hardware-enforced. */ - BLOCK_MODE = TagType.ENUM_REP | 4, + BLOCK_MODE = 536870916, /** * Tag::DIGEST specifies the digest algorithms that may be used with the key to perform signing @@ -91,7 +91,7 @@ enum Tag { * * Must be hardware-enforced. */ - DIGEST = TagType.ENUM_REP | 5, + DIGEST = 536870917, /** * Tag::PADDING specifies the padding modes that may be used with the key. This tag is relevant @@ -119,7 +119,7 @@ enum Tag { * * Must be hardware-enforced. */ - PADDING = TagType.ENUM_REP | 6, + PADDING = 536870918, /** * Tag::CALLER_NONCE specifies that the caller can provide a nonce for nonce-requiring @@ -132,7 +132,7 @@ enum Tag { * * Must be hardware-enforced. */ - CALLER_NONCE = TagType.BOOL | 7, + CALLER_NONCE = 1879048199, /** * Tag::MIN_MAC_LENGTH specifies the minimum length of MAC that can be requested or verified @@ -145,7 +145,7 @@ enum Tag { * * Must be hardware-enforced. */ - MIN_MAC_LENGTH = TagType.UINT | 8, + MIN_MAC_LENGTH = 805306376, // Tag 9 reserved @@ -155,7 +155,7 @@ enum Tag { * * Must be hardware-enforced. */ - EC_CURVE = TagType.ENUM | 10, + EC_CURVE = 268435466, /** * Tag::RSA_PUBLIC_EXPONENT specifies the value of the public exponent for an RSA key pair. @@ -169,7 +169,7 @@ enum Tag { * * Must be hardware-enforced. */ - RSA_PUBLIC_EXPONENT = TagType.ULONG | 200, + RSA_PUBLIC_EXPONENT = 1342177480, // Tag 201 reserved @@ -180,7 +180,7 @@ enum Tag { * * Must be hardware-enforced. */ - INCLUDE_UNIQUE_ID = TagType.BOOL | 202, + INCLUDE_UNIQUE_ID = 1879048394, /** * Tag::RSA_OAEP_MGF_DIGEST specifies the MGF1 digest algorithms that may be used with RSA @@ -199,7 +199,7 @@ enum Tag { * * Must be hardware-enforced. */ - RSA_OAEP_MGF_DIGEST = TagType.ENUM_REP | 203, + RSA_OAEP_MGF_DIGEST = 536871115, // Tag 301 reserved @@ -211,7 +211,7 @@ enum Tag { * * Must be hardware-enforced. */ - BOOTLOADER_ONLY = TagType.BOOL | 302, + BOOTLOADER_ONLY = 1879048494, /** * Tag::ROLLBACK_RESISTANCE specifies that the key has rollback resistance, meaning that when @@ -226,10 +226,10 @@ enum Tag { * * Must be hardware-enforced. */ - ROLLBACK_RESISTANCE = TagType.BOOL | 303, + ROLLBACK_RESISTANCE = 1879048495, // Reserved for future use. - HARDWARE_TYPE = TagType.ENUM | 304, + HARDWARE_TYPE = 268435760, /** * Keys tagged with EARLY_BOOT_ONLY may only be used during early boot, until @@ -238,7 +238,7 @@ enum Tag { * provided to IKeyMintDevice::importKey, the import must fail with * ErrorCode::EARLY_BOOT_ENDED. */ - EARLY_BOOT_ONLY = TagType.BOOL | 305, + EARLY_BOOT_ONLY = 1879048497, /** * Tag::ACTIVE_DATETIME specifies the date and time at which the key becomes active, in @@ -247,7 +247,7 @@ enum Tag { * * Need not be hardware-enforced. */ - ACTIVE_DATETIME = TagType.DATE | 400, + ACTIVE_DATETIME = 1610613136, /** * Tag::ORIGINATION_EXPIRE_DATETIME specifies the date and time at which the key expires for @@ -259,7 +259,7 @@ enum Tag { * * Need not be hardware-enforced. */ - ORIGINATION_EXPIRE_DATETIME = TagType.DATE | 401, + ORIGINATION_EXPIRE_DATETIME = 1610613137, /** * Tag::USAGE_EXPIRE_DATETIME specifies the date and time at which the key expires for @@ -271,7 +271,7 @@ enum Tag { * * Need not be hardware-enforced. */ - USAGE_EXPIRE_DATETIME = TagType.DATE | 402, + USAGE_EXPIRE_DATETIME = 1610613138, /** * OBSOLETE: Do not use. @@ -279,7 +279,7 @@ enum Tag { * This tag value is included for historical reason, as it was present in Keymaster. * KeyMint implementations do not need to support this tag. */ - MIN_SECONDS_BETWEEN_OPS = TagType.UINT | 403, + MIN_SECONDS_BETWEEN_OPS = 805306771, /** * Tag::MAX_USES_PER_BOOT specifies the maximum number of times that a key may be used between @@ -299,7 +299,7 @@ enum Tag { * * Must be hardware-enforced. */ - MAX_USES_PER_BOOT = TagType.UINT | 404, + MAX_USES_PER_BOOT = 805306772, /** * Tag::USAGE_COUNT_LIMIT specifies the number of times that a key may be used. This can be @@ -328,14 +328,14 @@ enum Tag { * record. This tag must have the same SecurityLevel as the tag that is added to the key * characteristics. */ - USAGE_COUNT_LIMIT = TagType.UINT | 405, + USAGE_COUNT_LIMIT = 805306773, /** * Tag::USER_ID specifies the ID of the Android user that is permitted to use the key. * * Must not be hardware-enforced. */ - USER_ID = TagType.UINT | 501, + USER_ID = 805306869, /** * Tag::USER_SECURE_ID specifies that a key may only be used under a particular secure user @@ -368,7 +368,7 @@ enum Tag { * * Must be hardware-enforced. */ - USER_SECURE_ID = TagType.ULONG_REP | 502, + USER_SECURE_ID = -1610612234, /** * Tag::NO_AUTH_REQUIRED specifies that no authentication is required to use this key. This tag @@ -376,7 +376,7 @@ enum Tag { * * Must be hardware-enforced. */ - NO_AUTH_REQUIRED = TagType.BOOL | 503, + NO_AUTH_REQUIRED = 1879048695, /** * Tag::USER_AUTH_TYPE specifies the types of user authenticators that may be used to authorize @@ -395,7 +395,7 @@ enum Tag { * * Must be hardware-enforced. */ - USER_AUTH_TYPE = TagType.ENUM | 504, + USER_AUTH_TYPE = 268435960, /** * Tag::AUTH_TIMEOUT specifies the time in seconds for which the key is authorized for use, @@ -409,7 +409,7 @@ enum Tag { * * Must be hardware-enforced. */ - AUTH_TIMEOUT = TagType.UINT | 505, + AUTH_TIMEOUT = 805306873, /** * Tag::ALLOW_WHILE_ON_BODY specifies that the key may be used after authentication timeout if @@ -417,7 +417,7 @@ enum Tag { * * Cannot be hardware-enforced. */ - ALLOW_WHILE_ON_BODY = TagType.BOOL | 506, + ALLOW_WHILE_ON_BODY = 1879048698, /** * TRUSTED_USER_PRESENCE_REQUIRED is an optional feature that specifies that this key must be @@ -445,7 +445,7 @@ enum Tag { * * Password authentication does not provide proof of presence to either TEE or StrongBox, * even if TEE or StrongBox does the password matching, because password input is handled by - * the non-secure world, which means an attacker who has compromised Android can spoof + * the non-secure world, which means an attacker who has compromised Android can spoif * password authentication. * * Note that no mechanism is defined for delivering proof of presence to an IKeyMintDevice, @@ -464,7 +464,7 @@ enum Tag { * * Must be hardware-enforced. */ - TRUSTED_USER_PRESENCE_REQUIRED = TagType.BOOL | 507, + TRUSTED_USER_PRESENCE_REQUIRED = 1879048699, /** * Tag::TRUSTED_CONFIRMATION_REQUIRED is only applicable to keys with KeyPurpose SIGN, and @@ -478,7 +478,7 @@ enum Tag { * * Must be hardware-enforced. */ - TRUSTED_CONFIRMATION_REQUIRED = TagType.BOOL | 508, + TRUSTED_CONFIRMATION_REQUIRED = 1879048700, /** * Tag::UNLOCKED_DEVICE_REQUIRED specifies that the key may only be used when the device is @@ -489,7 +489,7 @@ enum Tag { * even if implemented it does nothing because it was never enabled by Keystore. Refer to the * documentation for the deprecated method IKeyMintDevice::deviceLocked(). */ - UNLOCKED_DEVICE_REQUIRED = TagType.BOOL | 509, + UNLOCKED_DEVICE_REQUIRED = 1879048701, /** * Tag::APPLICATION_ID. When provided to generateKey or importKey, this tag specifies data @@ -507,7 +507,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - APPLICATION_ID = TagType.BYTES | 601, + APPLICATION_ID = -1879047591, /* * Semantically unenforceable tags, either because they have no specific meaning or because @@ -530,7 +530,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - APPLICATION_DATA = TagType.BYTES | 700, + APPLICATION_DATA = -1879047492, /** * Tag::CREATION_DATETIME specifies the date and time the key was created, in milliseconds since @@ -538,7 +538,7 @@ enum Tag { * * Must be in the software-enforced list, if provided. */ - CREATION_DATETIME = TagType.DATE | 701, + CREATION_DATETIME = 1610613437, /** * Tag::ORIGIN specifies where the key was created, if known. This tag must not be specified @@ -547,7 +547,7 @@ enum Tag { * * Must be hardware-enforced. */ - ORIGIN = TagType.ENUM | 702, + ORIGIN = 268436158, // 703 is unused. @@ -559,7 +559,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ROOT_OF_TRUST = TagType.BYTES | 704, + ROOT_OF_TRUST = -1879047488, /** * Tag::OS_VERSION specifies the system OS version with which the key may be used. This tag is @@ -582,7 +582,7 @@ enum Tag { * * Must be hardware-enforced. */ - OS_VERSION = TagType.UINT | 705, + OS_VERSION = 805307073, /** * Tag::OS_PATCHLEVEL specifies the system security patch level with which the key may be used. @@ -603,7 +603,7 @@ enum Tag { * * Must be hardware-enforced. */ - OS_PATCHLEVEL = TagType.UINT | 706, + OS_PATCHLEVEL = 805307074, /** * Tag::UNIQUE_ID specifies a unique, time-based identifier. This tag is never provided to or @@ -638,7 +638,7 @@ enum Tag { * * Must be hardware-enforced. */ - UNIQUE_ID = TagType.BYTES | 707, + UNIQUE_ID = -1879047485, /** * Tag::ATTESTATION_CHALLENGE is used to deliver a "challenge" value to the attested key @@ -649,7 +649,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_CHALLENGE = TagType.BYTES | 708, + ATTESTATION_CHALLENGE = -1879047484, /** * Tag::ATTESTATION_APPLICATION_ID identifies the set of applications which may use a key, used @@ -675,7 +675,7 @@ enum Tag { * * Cannot be hardware-enforced. */ - ATTESTATION_APPLICATION_ID = TagType.BYTES | 709, + ATTESTATION_APPLICATION_ID = -1879047483, /** * Tag::ATTESTATION_ID_BRAND provides the device's brand name, as returned by Build.BRAND in @@ -688,7 +688,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_BRAND = TagType.BYTES | 710, + ATTESTATION_ID_BRAND = -1879047482, /** * Tag::ATTESTATION_ID_DEVICE provides the device's device name, as returned by Build.DEVICE in @@ -701,7 +701,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_DEVICE = TagType.BYTES | 711, + ATTESTATION_ID_DEVICE = -1879047481, /** * Tag::ATTESTATION_ID_PRODUCT provides the device's product name, as returned by Build.PRODUCT @@ -714,7 +714,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_PRODUCT = TagType.BYTES | 712, + ATTESTATION_ID_PRODUCT = -1879047480, /** * Tag::ATTESTATION_ID_SERIAL the device's serial number. This field must be set only when @@ -726,7 +726,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_SERIAL = TagType.BYTES | 713, + ATTESTATION_ID_SERIAL = -1879047479, /** * Tag::ATTESTATION_ID_IMEI provides the IMEI one of the radios on the device to attested key @@ -740,7 +740,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_IMEI = TagType.BYTES | 714, + ATTESTATION_ID_IMEI = -1879047478, /** * Tag::ATTESTATION_ID_MEID provides the MEIDs for all radios on the device to attested key @@ -753,7 +753,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_MEID = TagType.BYTES | 715, + ATTESTATION_ID_MEID = -1879047477, /** * Tag::ATTESTATION_ID_MANUFACTURER provides the device's manufacturer name, as returned by @@ -766,7 +766,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_MANUFACTURER = TagType.BYTES | 716, + ATTESTATION_ID_MANUFACTURER = -1879047476, /** * Tag::ATTESTATION_ID_MODEL provides the device's model name, as returned by Build.MODEL in @@ -779,7 +779,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_MODEL = TagType.BYTES | 717, + ATTESTATION_ID_MODEL = -1879047475, /** * Tag::VENDOR_PATCHLEVEL specifies the vendor image security patch level with which the key may @@ -801,7 +801,7 @@ enum Tag { * * Must be hardware-enforced. */ - VENDOR_PATCHLEVEL = TagType.UINT | 718, + VENDOR_PATCHLEVEL = 805307086, /** * Tag::BOOT_PATCHLEVEL specifies the boot image (kernel) security patch level with which the @@ -821,7 +821,7 @@ enum Tag { * * Must be hardware-enforced. */ - BOOT_PATCHLEVEL = TagType.UINT | 719, + BOOT_PATCHLEVEL = 805307087, /** * DEVICE_UNIQUE_ATTESTATION is an argument to IKeyMintDevice::attested key generation/import @@ -858,7 +858,7 @@ enum Tag { * IKeyMintDevice implementations that support device-unique attestation MUST add the * DEVICE_UNIQUE_ATTESTATION tag to device-unique attestations. */ - DEVICE_UNIQUE_ATTESTATION = TagType.BOOL | 720, + DEVICE_UNIQUE_ATTESTATION = 1879048912, /** * IDENTITY_CREDENTIAL_KEY is never used by IKeyMintDevice, is not a valid argument to key @@ -866,7 +866,7 @@ enum Tag { * attestation. It is used in attestations produced by the IIdentityCredential HAL when that * HAL attests to Credential Keys. IIdentityCredential produces KeyMint-style attestations. */ - IDENTITY_CREDENTIAL_KEY = TagType.BOOL | 721, + IDENTITY_CREDENTIAL_KEY = 1879048913, /** * To prevent keys from being compromised if an attacker acquires read access to system / kernel @@ -884,7 +884,7 @@ enum Tag { * ErrorCode::INVALID_OPERATION is returned when a key with Tag::STORAGE_KEY is provided to * begin(). */ - STORAGE_KEY = TagType.BOOL | 722, + STORAGE_KEY = 1879048914, /** * Tag::ATTESTATION_ID_SECOND_IMEI provides an additional IMEI of one of the radios on the @@ -898,7 +898,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - ATTESTATION_ID_SECOND_IMEI = TagType.BYTES | 723, + ATTESTATION_ID_SECOND_IMEI = -1879047469, /** * Tag::MODULE_HASH specifies the SHA-256 hash of the DER-encoded module information (see @@ -912,7 +912,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - MODULE_HASH = TagType.BYTES | 724, + MODULE_HASH = -1879047468, /** * OBSOLETE: Do not use. @@ -922,7 +922,7 @@ enum Tag { * IKeymasterDevice::finish(). In KeyMint the IKeyMintOperation::updateAad() method is used for * this. */ - ASSOCIATED_DATA = TagType.BYTES | 1000, + ASSOCIATED_DATA = -1879047192, /** * Tag::NONCE is used to provide or return a nonce or Initialization Vector (IV) for AES-GCM, @@ -937,7 +937,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - NONCE = TagType.BYTES | 1001, + NONCE = -1879047191, /** * Tag::MAC_LENGTH provides the requested length of a MAC or GCM authentication tag, in bits. @@ -948,7 +948,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - MAC_LENGTH = TagType.UINT | 1003, + MAC_LENGTH = 805307371, /** * Tag::RESET_SINCE_ID_ROTATION specifies whether the device has been factory reset since the @@ -956,7 +956,7 @@ enum Tag { * * Must never appear in KeyCharacteristics. */ - RESET_SINCE_ID_ROTATION = TagType.BOOL | 1004, + RESET_SINCE_ID_ROTATION = 1879049196, /** * OBSOLETE: Do not use. @@ -966,7 +966,7 @@ enum Tag { * IKeymasterDevice::finish(). In KeyMint the IKeyMintOperation::finish() method includes * a confirmationToken argument for this. */ - CONFIRMATION_TOKEN = TagType.BYTES | 1005, + CONFIRMATION_TOKEN = -1879047187, /** * Tag::CERTIFICATE_SERIAL specifies the serial number to be assigned to the attestation @@ -974,7 +974,7 @@ enum Tag { * keyMint in the attestation parameters during generateKey() and importKey(). If not provided, * the serial shall default to 1. */ - CERTIFICATE_SERIAL = TagType.BIGNUM | 1006, + CERTIFICATE_SERIAL = -2147482642, /** * Tag::CERTIFICATE_SUBJECT the certificate subject. The value is a DER encoded X509 NAME. @@ -982,7 +982,7 @@ enum Tag { * during generateKey and importKey. If not provided the subject name shall default to * CN="Android Keystore Key". */ - CERTIFICATE_SUBJECT = TagType.BYTES | 1007, + CERTIFICATE_SUBJECT = -1879047185, /** * Tag::CERTIFICATE_NOT_BEFORE the beginning of the validity of the certificate in UNIX epoch @@ -992,7 +992,7 @@ enum Tag { * to specify the value of this tag for a wrapped asymmetric key, so a value of 0 is suggested * for certificate generation. */ - CERTIFICATE_NOT_BEFORE = TagType.DATE | 1008, + CERTIFICATE_NOT_BEFORE = 1610613744, /** * Tag::CERTIFICATE_NOT_AFTER the end of the validity of the certificate in UNIX epoch time in @@ -1002,7 +1002,7 @@ enum Tag { * wrapped asymmetric key, so a value of 253402300799000 is suggested for certificate * generation. */ - CERTIFICATE_NOT_AFTER = TagType.DATE | 1009, + CERTIFICATE_NOT_AFTER = 1610613745, /** * Tag::MAX_BOOT_LEVEL specifies a maximum boot level at which a key should function. @@ -1013,5 +1013,5 @@ enum Tag { * * Cannot be hardware enforced in this version. */ - MAX_BOOT_LEVEL = TagType.UINT | 1010, -} + MAX_BOOT_LEVEL = 805307378, +} \ No newline at end of file diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index 1ed6c89..1eaa2a3 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -50,6 +50,7 @@ use kmr_crypto_boring::ec::BoringEc; use kmr_crypto_boring::hmac::BoringHmac; use kmr_crypto_boring::rng::BoringRng; use kmr_crypto_boring::rsa::BoringRsa; +use kmr_common::crypto::Rng; use kmr_ta::device::CsrSigningAlgorithm; use kmr_ta::{HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; use kmr_wire::keymint::{AttestationKey, KeyParam}; @@ -1227,7 +1228,39 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { rpc, }; - Ok(KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev)) + let mut ta = KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev); + + let mut rng = BoringRng {}; + + let mut vb_hash = vec![0u8; 32]; + rng.fill_bytes(&mut vb_hash); + let mut vb_key = vec![0u8; 32]; + rng.fill_bytes(&mut vb_key); + + let req = PerformOpReq::SetBootInfo(kmr_wire::SetBootInfoRequest { + verified_boot_state: 0, // Verified + verified_boot_hash: vb_hash, + verified_boot_key: vb_key, + device_boot_locked: true, + boot_patchlevel: 20250605, + }); + let resp = ta.process_req(req); + if resp.error_code != 0 { + return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)) + .context(err!("Failed to set boot info")); + } + let req = PerformOpReq::SetHalInfo(kmr_wire::SetHalInfoRequest { + os_version: 35, + os_patchlevel: 202506, + vendor_patchlevel: 202506, + }); + let resp = ta.process_req(req); + if resp.error_code != 0 { + return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)) + .context(err!("Failed to set HAL info")); + } + + Ok(ta) } pub fn get_keymint_device<'a>( diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 7052aeb..177d46a 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -19,18 +19,18 @@ use crate::{ err, global::{DB, ENFORCEMENTS, SUPER_KEY, UNDEFINED_NOT_AFTER}, keymaster::{ - attestation_key_utils::{get_attest_key_info, AttestationKeyInfo}, + attestation_key_utils::{AttestationKeyInfo, get_attest_key_info}, db::{ BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, Uuid, }, - error::{into_logged_binder, map_binder_status, KsError}, + error::{KsError, into_logged_binder, map_binder_status}, keymint_device::get_keymint_wrapper, metrics_store::log_key_creation_event_stats, operation::{KeystoreOperation, LoggingInfo, OperationDb}, super_key::{KeyBlob, SuperKeyManager}, - utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, + utils::{key_characteristics_to_internal, key_param_to_aidl, key_parameters_to_authorizations, log_params}, }, plat::utils::multiuser_get_user_id, }; @@ -41,7 +41,7 @@ use crate::keymaster::key_parameter::KeyParameterValue as KsKeyParamValue; use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; -use kmr_wire::keymint::KeyMintHardwareInfo; +use kmr_wire::keymint::{KeyMintHardwareInfo, KeyParam}; use log::debug; use rsbinder::{thread_state::CallingContext, Interface, Status}; @@ -113,8 +113,13 @@ impl KeystoreSecurityLevel { &self, uid: u32, params: &[KeyParameter], - _key: &KeyDescriptor, + key: &KeyDescriptor, ) -> Result> { + debug!( + "KeystoreSecurityLevel::add_required_parameters: params={:?}, key={:?}", + log_params(params), + key + ); let mut result = params.to_vec(); // Prevent callers from specifying the CREATION_DATETIME tag. @@ -152,6 +157,8 @@ impl KeystoreSecurityLevel { // If there is an attestation challenge we need to get an application id. if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) { + let _wp = + self.watch(" KeystoreSecurityLevel::add_required_parameters: calling get_aaid"); match crate::plat::utils::get_aaid(uid) { Ok(aaid_ok) => { result.push(KeyParameter { @@ -159,7 +166,9 @@ impl KeystoreSecurityLevel { value: KeyParameterValue::Blob(aaid_ok.into_bytes()), }); } - Err(e) => return Err(anyhow!(e)).context(err!("Attestation ID retrieval error.")), + Err(e) => { + return Err(anyhow!(e)).context(err!("Attestation ID retrieval error.")) + } } } @@ -171,6 +180,16 @@ impl KeystoreSecurityLevel { // "Caller does not have the permission to generate a unique ID" // )); // } + // if self + // .id_rotation_state + // .had_factory_reset_since_id_rotation(&creation_datetime) + // .context(err!("Call to had_factory_reset_since_id_rotation failed."))? + // { + // result.push(KeyParameter { + // tag: Tag::RESET_SINCE_ID_ROTATION, + // value: KeyParameterValue::BoolValue(true), + // }) + // } // } // If the caller requests any device identifier attestation tag, check that they hold the @@ -184,18 +203,9 @@ impl KeystoreSecurityLevel { // If we are generating/importing an asymmetric key, we need to make sure // that NOT_BEFORE and NOT_AFTER are present. match params.iter().find(|kp| kp.tag == Tag::ALGORITHM) { - Some(KeyParameter { - tag: _, - value: KeyParameterValue::Algorithm(Algorithm::RSA), - }) - | Some(KeyParameter { - tag: _, - value: KeyParameterValue::Algorithm(Algorithm::EC), - }) => { - if !params - .iter() - .any(|kp| kp.tag == Tag::CERTIFICATE_NOT_BEFORE) - { + Some(KeyParameter { tag: _, value: KeyParameterValue::Algorithm(Algorithm::RSA) }) + | Some(KeyParameter { tag: _, value: KeyParameterValue::Algorithm(Algorithm::EC) }) => { + if !params.iter().any(|kp| kp.tag == Tag::CERTIFICATE_NOT_BEFORE) { result.push(KeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0), @@ -380,6 +390,8 @@ impl KeystoreSecurityLevel { let calling_context = CallingContext::default(); let calling_uid = calling_context.uid; + debug!("KeystoreSecurityLevel::generate_key: uid={:?} key={:?}, attest_key_descriptor={:?}, params={:?}, flags={}", calling_uid, key, attest_key_descriptor, log_params(params), flags); + let key = match key.domain { Domain::APP => KeyDescriptor { domain: key.domain, diff --git a/src/logging.rs b/src/logging.rs index 7c18210..bba5cc3 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -22,7 +22,7 @@ pub fn init_logger() { pub fn init_logger() { android_logger::init_once( android_logger::Config::default() - .with_max_level(LevelFilter::Trace) + .with_max_level(LevelFilter::Debug) .with_tag("OhMyKeymint"), ); } diff --git a/src/main.rs b/src/main.rs index 0f9ee7c..268431f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,11 @@ use std::panic; use log::{debug, error}; +use rsbinder::hub; -use crate:: +use crate::{ android::system::keystore2:: - IKeystoreOperation + IKeystoreOperation, keymaster::service::KeystoreService} ; @@ -31,17 +32,26 @@ const TAG: &str = "OhMyKeymint"; fn main() { logging::init_logger(); + debug!("Hello, OhMyKeymint!"); + debug!("Initial process state"); rsbinder::ProcessState::init_default(); - let db = keymaster::db::KeymasterDb::new().unwrap(); - db.close().unwrap(); // Redirect panic messages to logcat. panic::set_hook(Box::new(|panic_info| { error!("{}", panic_info); })); - debug!("Hello, OhMyKeymint!"); + debug!("Starting thread pool"); + rsbinder::ProcessState::start_thread_pool(); + + debug!("Creating keystore service"); + let service = KeystoreService::new_native_binder().unwrap(); + + debug!("Adding service to hub"); + hub::add_service("omk", service.as_binder()).unwrap(); + debug!("Joining thread pool"); + rsbinder::ProcessState::join_thread_pool().unwrap(); } #[derive(Clone)] diff --git a/ta/src/lib.rs b/ta/src/lib.rs index 9a49ab8..dd25e43 100644 --- a/ta/src/lib.rs +++ b/ta/src/lib.rs @@ -318,7 +318,7 @@ impl KeyMintTa { Self { imp, dev, - in_early_boot: true, + in_early_boot: false, device_hmac: None, rot_challenge: [0; 16], // Work around Rust limitation that `vec![None; n]` doesn't work. @@ -329,7 +329,13 @@ impl KeyMintTa { hw_info, rpc_info, aidl_version: KEYMINT_CURRENT_VERSION, - boot_info: None, + boot_info: Some(keymint::BootInfo { + verified_boot_key: vec![], + device_boot_locked: false, + verified_boot_state: VerifiedBootState::Verified, + verified_boot_hash: vec![], + boot_patchlevel: 20250605, + }), rot_data: None, hal_info: None, attestation_chain_info: RefCell::new(BTreeMap::new()), @@ -578,43 +584,18 @@ impl KeyMintTa { /// method when this information arrives from the bootloader (which happens in an /// implementation-specific manner). pub fn set_boot_info(&mut self, boot_info: keymint::BootInfo) -> Result<(), Error> { - if !self.in_early_boot { - error!( - "Rejecting attempt to set boot info {:?} after early boot", - boot_info - ); - return Err(km_err!( - EarlyBootEnded, - "attempt to set boot info to {boot_info:?} after early boot" - )); - } - if let Some(existing_boot_info) = &self.boot_info { - if *existing_boot_info == boot_info { - warn!( - "Boot info already set, ignoring second attempt to set same values {:?}", - boot_info - ); - } else { - return Err(km_err!( - RootOfTrustAlreadySet, - "attempt to set boot info to {:?} but already set to {:?}", - boot_info, - existing_boot_info - )); - } - } else { - info!("Setting boot_info to {:?}", boot_info); - let rot_info = RootOfTrustInfo { - verified_boot_key: boot_info.verified_boot_key.clone(), - device_boot_locked: boot_info.device_boot_locked, - verified_boot_state: boot_info.verified_boot_state, - }; - self.boot_info = Some(boot_info); - self.rot_data = - Some(rot_info.into_vec().map_err(|e| { - km_err!(EncodingError, "failed to encode root-of-trust: {:?}", e) - })?); - } + info!("Setting boot_info to {:?}", boot_info); + let rot_info = RootOfTrustInfo { + verified_boot_key: boot_info.verified_boot_key.clone(), + device_boot_locked: boot_info.device_boot_locked, + verified_boot_state: boot_info.verified_boot_state, + }; + self.boot_info = Some(boot_info); + self.rot_data = Some( + rot_info + .into_vec() + .map_err(|e| km_err!(EncodingError, "failed to encode root-of-trust: {:?}", e))?, + ); Ok(()) } From 8ee843280b026ff08513341f4be5b60b556112ca Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sun, 12 Oct 2025 03:57:49 +0800 Subject: [PATCH 13/46] add gc Signed-off-by: qwq233 --- src/global.rs | 41 +++- src/keymaster/async_task.rs | 258 +++++++++++++++++++++++ src/keymaster/db.rs | 350 +++++++++++++++++++++++++++----- src/keymaster/gc.rs | 156 ++++++++++++++ src/keymaster/keymint_device.rs | 19 ++ src/keymaster/mod.rs | 2 + src/keymaster/security_level.rs | 4 +- src/keymaster/service.rs | 2 + 8 files changed, 773 insertions(+), 59 deletions(-) create mode 100644 src/keymaster/async_task.rs create mode 100644 src/keymaster/gc.rs diff --git a/src/global.rs b/src/global.rs index 1a0ee4e..e966ebf 100644 --- a/src/global.rs +++ b/src/global.rs @@ -8,13 +8,48 @@ use rsbinder::Strong; use anyhow::{Context, Result}; use crate::{ - android::hardware::security::secureclock::ISecureClock::ISecureClock, + android::hardware::security::{keymint::SecurityLevel::SecurityLevel, secureclock::ISecureClock::ISecureClock}, err, - keymaster::{db::KeymasterDb, enforcements::Enforcements, super_key::SuperKeyManager}, + keymaster::{async_task::AsyncTask, db::KeymasterDb, enforcements::Enforcements, gc::Gc, keymint_device::get_keymint_wrapper, super_key::SuperKeyManager}, watchdog as wd, }; static DB_INIT: Once = Once::new(); +/// A single on-demand worker thread that handles deferred tasks with two different +/// priorities. +pub static ASYNC_TASK: LazyLock> = LazyLock::new(Default::default); + +static GC: LazyLock> = LazyLock::new(|| { + Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || { + ( + Box::new(|uuid, blob| { + let security_level = u128::from_be_bytes(uuid.0) as u32; + let security_level = match security_level { + 100 => SecurityLevel::KEYSTORE, + 1 => SecurityLevel::TRUSTED_ENVIRONMENT, + 2 => SecurityLevel::STRONGBOX, + _ => { + return Err(anyhow::anyhow!( + "Invalid security level {security_level} in UUID" + )) + } + }; + + let km_dev = get_keymint_wrapper(security_level).unwrap(); + let _wp = wd::watch("invalidate key closure: calling IKeyMintDevice::deleteKey"); + km_dev.delete_Key(blob) + .map_err(|e| anyhow::anyhow!("Failed to delete key blob: {e}")) + .context(err!("Trying to invalidate key blob.")) + }), + KeymasterDb::new( + None + ) + .expect("Failed to open database"), + SUPER_KEY.clone(), + ) + })) +}); + /// Open a connection to the Keystore 2.0 database. This is called during the initialization of /// the thread local DB field. It should never be called directly. The first time this is called /// we also call KeystoreDB::cleanup_leftovers to restore the key lifecycle invariant. See the @@ -24,7 +59,7 @@ static DB_INIT: Once = Once::new(); /// is run only once, as long as the ASYNC_TASK instance is the same. So only one additional /// database connection is created for the garbage collector worker. pub fn create_thread_local_db() -> KeymasterDb { - let result = KeymasterDb::new(); + let result = KeymasterDb::new(Some(GC.clone())); let mut db = match result { Ok(db) => db, Err(e) => { diff --git a/src/keymaster/async_task.rs b/src/keymaster/async_task.rs new file mode 100644 index 0000000..eb432ec --- /dev/null +++ b/src/keymaster/async_task.rs @@ -0,0 +1,258 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module implements the handling of async tasks. +//! The worker thread has a high priority and a low priority queue. Adding a job to either +//! will cause one thread to be spawned if none exists. As a compromise between performance +//! and resource consumption, the thread will linger for about 30 seconds after it has +//! processed all tasks before it terminates. +//! Note that low priority tasks are processed only when the high priority queue is empty. + +use std::{any::Any, any::TypeId, time::Duration}; +use std::{ + collections::{HashMap, VecDeque}, + sync::Arc, + sync::{Condvar, Mutex, MutexGuard}, + thread, +}; + +#[derive(Debug, PartialEq, Eq)] +enum State { + Exiting, + Running, +} + +/// The Shelf allows async tasks to store state across invocations. +/// Note: Store elves at your own peril ;-). +#[derive(Debug, Default)] +pub struct Shelf(HashMap>); + +impl Shelf { + /// Get a reference to the shelved data of type T. Returns Some if the data exists. + pub fn get_downcast_ref(&self) -> Option<&T> { + self.0.get(&TypeId::of::()).and_then(|v| v.downcast_ref::()) + } + + /// Get a mutable reference to the shelved data of type T. If a T was inserted using put, + /// get_mut, or get_or_put_with. + pub fn get_downcast_mut(&mut self) -> Option<&mut T> { + self.0.get_mut(&TypeId::of::()).and_then(|v| v.downcast_mut::()) + } + + /// Remove the entry of the given type and returns the stored data if it existed. + pub fn remove_downcast_ref(&mut self) -> Option { + self.0.remove(&TypeId::of::()).and_then(|v| v.downcast::().ok().map(|b| *b)) + } + + /// Puts data `v` on the shelf. If there already was an entry of type T it is returned. + pub fn put(&mut self, v: T) -> Option { + self.0 + .insert(TypeId::of::(), Box::new(v) as Box) + .and_then(|v| v.downcast::().ok().map(|b| *b)) + } + + /// Gets a mutable reference to the entry of the given type and default creates it if necessary. + /// The type must implement Default. + pub fn get_mut(&mut self) -> &mut T { + self.0 + .entry(TypeId::of::()) + .or_insert_with(|| Box::::default() as Box) + .downcast_mut::() + .unwrap() + } + + /// Gets a mutable reference to the entry of the given type or creates it using the init + /// function. Init is not executed if the entry already existed. + pub fn get_or_put_with(&mut self, init: F) -> &mut T + where + F: FnOnce() -> T, + { + self.0 + .entry(TypeId::of::()) + .or_insert_with(|| Box::new(init()) as Box) + .downcast_mut::() + .unwrap() + } +} + +struct AsyncTaskState { + state: State, + thread: Option>, + timeout: Duration, + hi_prio_req: VecDeque>, + lo_prio_req: VecDeque>, + idle_fns: Vec>, + /// The store allows tasks to store state across invocations. It is passed to each invocation + /// of each task. Tasks need to cooperate on the ids they use for storing state. + shelf: Option, +} + +/// AsyncTask spawns one worker thread on demand to process jobs inserted into +/// a low and a high priority work queue. The queues are processed FIFO, and low +/// priority queue is processed if the high priority queue is empty. +/// Note: Because there is only one worker thread at a time for a given AsyncTask instance, +/// all scheduled requests are guaranteed to be serialized with respect to one another. +pub struct AsyncTask { + state: Arc<(Condvar, Mutex)>, +} + +impl Default for AsyncTask { + fn default() -> Self { + Self::new(Duration::from_secs(30)) + } +} + +impl AsyncTask { + /// Construct an [`AsyncTask`] with a specific timeout value. + pub fn new(timeout: Duration) -> Self { + Self { + state: Arc::new(( + Condvar::new(), + Mutex::new(AsyncTaskState { + state: State::Exiting, + thread: None, + timeout, + hi_prio_req: VecDeque::new(), + lo_prio_req: VecDeque::new(), + idle_fns: Vec::new(), + shelf: None, + }), + )), + } + } + + /// Adds a one-off job to the high priority queue. High priority jobs are + /// completed before low priority jobs and can also overtake low priority + /// jobs. But they cannot preempt them. + pub fn queue_hi(&self, f: F) + where + F: for<'r> FnOnce(&'r mut Shelf) + Send + 'static, + { + self.queue(f, true) + } + + /// Adds a one-off job to the low priority queue. Low priority jobs are + /// completed after high priority. And they are not executed as long as high + /// priority jobs are present. Jobs always run to completion and are never + /// preempted by high priority jobs. + pub fn queue_lo(&self, f: F) + where + F: FnOnce(&mut Shelf) + Send + 'static, + { + self.queue(f, false) + } + + /// Adds an idle callback. This will be invoked whenever the worker becomes + /// idle (all high and low priority jobs have been performed). + pub fn add_idle(&self, f: F) + where + F: Fn(&mut Shelf) + Send + Sync + 'static, + { + let (ref _condvar, ref state) = *self.state; + let mut state = state.lock().unwrap(); + state.idle_fns.push(Arc::new(f)); + } + + fn queue(&self, f: F, hi_prio: bool) + where + F: for<'r> FnOnce(&'r mut Shelf) + Send + 'static, + { + let (ref condvar, ref state) = *self.state; + let mut state = state.lock().unwrap(); + + if hi_prio { + state.hi_prio_req.push_back(Box::new(f)); + } else { + state.lo_prio_req.push_back(Box::new(f)); + } + + if state.state != State::Running { + self.spawn_thread(&mut state); + } + drop(state); + condvar.notify_all(); + } + + fn spawn_thread(&self, state: &mut MutexGuard) { + if let Some(t) = state.thread.take() { + t.join().expect("AsyncTask panicked."); + } + + let cloned_state = self.state.clone(); + let timeout_period = state.timeout; + + state.thread = Some(thread::spawn(move || { + let (ref condvar, ref state) = *cloned_state; + + enum Action { + QueuedFn(Box), + IdleFns(Vec>), + } + let mut done_idle = false; + + // When the worker starts, it takes the shelf and puts it on the stack. + let mut shelf = state.lock().unwrap().shelf.take().unwrap_or_default(); + loop { + if let Some(action) = { + let state = state.lock().unwrap(); + if !done_idle && state.hi_prio_req.is_empty() && state.lo_prio_req.is_empty() { + // No jobs queued so invoke the idle callbacks. + Some(Action::IdleFns(state.idle_fns.clone())) + } else { + // Wait for either a queued job to arrive or a timeout. + let (mut state, timeout) = condvar + .wait_timeout_while(state, timeout_period, |state| { + state.hi_prio_req.is_empty() && state.lo_prio_req.is_empty() + }) + .unwrap(); + match ( + state.hi_prio_req.pop_front(), + state.lo_prio_req.is_empty(), + timeout.timed_out(), + ) { + (Some(f), _, _) => Some(Action::QueuedFn(f)), + (None, false, _) => { + state.lo_prio_req.pop_front().map(|f| Action::QueuedFn(f)) + } + (None, true, true) => { + // When the worker exits it puts the shelf back into the shared + // state for the next worker to use. So state is preserved not + // only across invocations but also across worker thread shut down. + state.shelf = Some(shelf); + state.state = State::Exiting; + break; + } + (None, true, false) => None, + } + } + } { + // Now that the lock has been dropped, perform the action. + match action { + Action::QueuedFn(f) => { + f(&mut shelf); + done_idle = false; + } + Action::IdleFns(idle_fns) => { + for idle_fn in idle_fns { + idle_fn(&mut shelf); + } + done_idle = true; + } + } + } + } + })); + state.state = State::Running; + } +} diff --git a/src/keymaster/db.rs b/src/keymaster/db.rs index c05194f..49b6fef 100644 --- a/src/keymaster/db.rs +++ b/src/keymaster/db.rs @@ -19,7 +19,7 @@ use crate::{ hardware::security::keymint::ErrorCode::ErrorCode, security::metrics::{Storage::Storage as MetricsStorage, StorageStats::StorageStats}, }, - keymaster::{database::perboot::PerbootDB, super_key::SuperKeyType}, + keymaster::{database::perboot::PerbootDB, gc::Gc, super_key::SuperKeyType}, plat::utils::AID_USER_OFFSET, watchdog as wd, }; @@ -83,15 +83,56 @@ use crate::{ utils::get_current_time_in_milliseconds, }; +/// This trait is private to the database module. It is used to convey whether or not the garbage +/// collector shall be invoked after a database access. All closures passed to +/// `KeystoreDB::with_transaction` return a tuple (bool, T) where the bool indicates if the +/// gc needs to be triggered. This convenience function allows to turn any anyhow::Result +/// into anyhow::Result<(bool, T)> by simply appending one of `.do_gc(bool)`, `.no_gc()`, or +/// `.need_gc()`. +trait DoGc { + fn do_gc(self, need_gc: bool) -> Result<(bool, T)>; + + fn no_gc(self) -> Result<(bool, T)>; + + fn need_gc(self) -> Result<(bool, T)>; +} + +impl DoGc for Result { + fn do_gc(self, need_gc: bool) -> Result<(bool, T)> { + self.map(|r| (need_gc, r)) + } + + fn no_gc(self) -> Result<(bool, T)> { + self.do_gc(false) + } + + fn need_gc(self) -> Result<(bool, T)> { + self.do_gc(true) + } +} + +/// Information about a superseded blob (a blob that is no longer the +/// most recent blob of that type for a given key, due to upgrade or +/// replacement). +pub struct SupersededBlob { + /// ID + pub blob_id: i64, + /// Contents. + pub blob: Vec, + /// Metadata. + pub metadata: BlobMetaData, +} + pub struct KeymasterDb { conn: Connection, + gc: Option>, perboot: Arc, } impl KeymasterDb { const UNASSIGNED_KEY_ID: i64 = -1i64; - pub fn new() -> Result { + pub fn new(gc: Option>) -> Result { let _wp = wd::watch("KeystoreDB::new"); let path = format!("{}/{}", DB_ROOT_PATH, PERSISTENT_DB_FILENAME); @@ -103,13 +144,14 @@ impl KeymasterDb { let init_table = conn.transaction().context(err!( "Failed to create transaction for initializing database." ))?; - Self::init_tables(&init_table)?; + Self::init_tables(&init_table).no_gc()?; init_table.commit().context(err!( "Failed to commit transaction for initializing database." ))?; Ok(KeymasterDb { conn, + gc, perboot: crate::keymaster::database::perboot::PERBOOT_DB.clone(), }) } @@ -289,19 +331,21 @@ impl KeymasterDb { /// or DatabaseLocked is encountered. fn with_transaction(&mut self, behavior: TransactionBehavior, f: F) -> Result where - F: Fn(&Transaction) -> Result, + F: Fn(&Transaction) -> Result<(bool, T)>, { - let name = behavior.name().unwrap_or(""); + let name = behavior.name(); loop { let result = self .conn .transaction_with_behavior(behavior.into()) - .context("Failed to create transaction.") + .context(err!()) .and_then(|tx| { - let value = f(&tx)?; - tx.commit() - .context(err!("Failed to commit transaction: {}", name))?; - Ok(value) + let _wp = name.map(wd::watch); + f(&tx).map(|result| (result, tx)) + }) + .and_then(|(result, tx)| { + tx.commit().context(err!("Failed to commit transaction."))?; + Ok(result) }); match result { Ok(result) => break Ok(result), @@ -310,11 +354,161 @@ impl KeymasterDb { std::thread::sleep(DB_BUSY_RETRY_INTERVAL); continue; } else { - return Err(e).context(err!("Failed to process transaction: {}", name)); + return Err(e).context(err!()); } } } } + .map(|(need_gc, result)| { + if need_gc { + if let Some(ref gc) = self.gc { + gc.notify_gc(); + } + } + result + }) + } + + fn cleanup_unreferenced(tx: &Transaction) -> Result<()> { + let _wp = wd::watch("KeystoreDB::cleanup_unreferenced"); + { + tx.execute( + "DELETE FROM persistent.keymetadata + WHERE keyentryid IN ( + SELECT id FROM persistent.keyentry + WHERE state = ? + );", + params![KeyLifeCycle::Unreferenced], + ) + .context("Trying to delete keymetadata.")?; + tx.execute( + "DELETE FROM persistent.keyparameter + WHERE keyentryid IN ( + SELECT id FROM persistent.keyentry + WHERE state = ? + );", + params![KeyLifeCycle::Unreferenced], + ) + .context("Trying to delete keyparameters.")?; + tx.execute( + "DELETE FROM persistent.grant + WHERE keyentryid IN ( + SELECT id FROM persistent.keyentry + WHERE state = ? + );", + params![KeyLifeCycle::Unreferenced], + ) + .context("Trying to delete grants.")?; + tx.execute( + "DELETE FROM persistent.keyentry + WHERE state = ?;", + params![KeyLifeCycle::Unreferenced], + ) + .context("Trying to delete keyentry.")?; + Result::<()>::Ok(()) + } + .context(err!()) + } + + /// This function is intended to be used by the garbage collector. + /// It deletes the blobs given by `blob_ids_to_delete`. It then tries to find up to `max_blobs` + /// superseded key blobs that might need special handling by the garbage collector. + /// If no further superseded blobs can be found it deletes all other superseded blobs that don't + /// need special handling and returns None. + pub fn handle_next_superseded_blobs( + &mut self, + blob_ids_to_delete: &[i64], + max_blobs: usize, + ) -> Result> { + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob"); + let result = self + .with_transaction(Immediate("TX_handle_next_superseded_blob"), |tx| { + // Delete the given blobs. + for blob_id in blob_ids_to_delete { + tx.execute( + "DELETE FROM persistent.blobmetadata WHERE blobentryid = ?;", + params![blob_id], + ) + .context(err!("Trying to delete blob metadata: {:?}", blob_id))?; + tx.execute( + "DELETE FROM persistent.blobentry WHERE id = ?;", + params![blob_id], + ) + .context(err!("Trying to delete blob: {:?}", blob_id))?; + } + + Self::cleanup_unreferenced(tx).context("Trying to cleanup unreferenced.")?; + + // Find up to `max_blobs` more out-of-date key blobs, load their metadata and return it. + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob find_next v1"); + let mut stmt = tx + .prepare( + "SELECT id, blob FROM persistent.blobentry + WHERE subcomponent_type = ? + AND ( + id NOT IN ( + SELECT MAX(id) FROM persistent.blobentry + WHERE subcomponent_type = ? + GROUP BY keyentryid, subcomponent_type + ) + OR keyentryid NOT IN (SELECT id FROM persistent.keyentry) + ) LIMIT ?;", + ) + .context("Trying to prepare query for superseded blobs.")?; + + let rows = stmt + .query_map( + params![ + SubComponentType::KEY_BLOB, + SubComponentType::KEY_BLOB, + max_blobs as i64, + ], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .context("Trying to query superseded blob.")?; + + let result = rows + .collect::)>, rusqlite::Error>>() + .context("Trying to extract superseded blobs.")?; + + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob load_metadata"); + let result = result + .into_iter() + .map(|(blob_id, blob)| { + Ok(SupersededBlob { + blob_id, + blob, + metadata: BlobMetaData::load_from_db(blob_id, tx)?, + }) + }) + .collect::>>() + .context("Trying to load blob metadata.")?; + if !result.is_empty() { + return Ok(result).no_gc(); + } + + // We did not find any out-of-date key blobs, so let's remove other types of superseded + // blob in one transaction. + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob delete v1"); + tx.execute( + "DELETE FROM persistent.blobentry + WHERE NOT subcomponent_type = ? + AND ( + id NOT IN ( + SELECT MAX(id) FROM persistent.blobentry + WHERE NOT subcomponent_type = ? + GROUP BY keyentryid, subcomponent_type + ) OR keyentryid NOT IN (SELECT id FROM persistent.keyentry) + );", + params![SubComponentType::KEY_BLOB, SubComponentType::KEY_BLOB], + ) + .context("Trying to purge superseded blobs.")?; + + Ok(vec![]).no_gc() + }) + .context(err!())?; + + Result::Ok(result) } /// This maintenance function should be called only once before the database is used for the @@ -332,6 +526,7 @@ impl KeymasterDb { params![KeyLifeCycle::Unreferenced, KeyLifeCycle::Existing], ) .context("Failed to execute query.") + .need_gc() }) .context("Failed to cleanup leftovers.") } @@ -370,13 +565,11 @@ impl KeymasterDb { "DELETE FROM persistent.grant WHERE keyentryid = ?;", params![key_id], ) - .context("Trying to delete grants to other apps.")?; + .context("Trying to delete grants.")?; // The associated blobentry rows are not immediately deleted when the owning keyentry is // removed, because a KeyMint `deleteKey()` invocation is needed (specifically for the - // `KEY_BLOB`). That should not be done from within the database transaction. Also, calls - // to `deleteKey()` need to be delayed until the boot has completed, to avoid making - // permanent changes during an OTA before the point of no return. Mark the affected rows - // with `state=Orphaned` so a subsequent garbage collection can do the `deleteKey()`. + // `KEY_BLOB`). Mark the affected rows with `state=Orphaned` so a subsequent garbage + // collection can do this. tx.execute( "UPDATE persistent.blobentry SET state = ? WHERE keyentryid = ?", params![BlobState::Orphaned, key_id], @@ -558,7 +751,11 @@ impl KeymasterDb { .get(0) .context("Failed to unpack id.") }) - .context(err!("In load_key_entry_id: Failed to load key entry id.")) + .context(err!( + "In load_key_entry_id: Failed to load key entry id. keyType={:?} key={:?}", + key_type, + key + )) } /// Checks if a key exists with given key type and key descriptor properties. @@ -584,6 +781,7 @@ impl KeymasterDb { _ => Err(error).context(err!("Failed to find if the key exists.")), }, } + .no_gc() }) .context(err!("Failed to check if key exists.")) } @@ -932,7 +1130,13 @@ impl KeymasterDb { "UPDATE persistent.keyentry SET alias = NULL, domain = NULL, namespace = NULL, state = ? WHERE alias = ? AND domain = ? AND namespace = ? AND key_type = ?;", - params![KeyLifeCycle::Unreferenced, alias, domain.0 as u32, namespace, key_type], + params![ + KeyLifeCycle::Unreferenced, + alias, + domain.0 as u32, + namespace, + key_type + ], ) .context(err!("Failed to rebind existing entry."))?; let result = tx @@ -960,7 +1164,6 @@ impl KeymasterDb { Ok(updated != 0) } - /// Store a new key in a single transaction. /// The function creates a new key entry, populates the blob, key parameter, and metadata /// fields, and rebinds the given alias to the new key. @@ -1010,6 +1213,19 @@ impl KeymasterDb { // database here and then immediately replaced by the superseding blob. // The garbage collector will then subject the blob to deleteKey of the // KM back end to permanently invalidate the key. + let need_gc = if let Some((blob, blob_metadata)) = superseded_blob { + Self::set_blob_internal( + tx, + key_id.id(), + SubComponentType::KEY_BLOB, + Some(blob), + Some(blob_metadata), + ) + .context("Trying to insert superseded key blob.")?; + true + } else { + false + }; Self::set_blob_internal( tx, @@ -1038,8 +1254,10 @@ impl KeymasterDb { metadata .store_in_db(key_id.id(), tx) .context("Trying to insert key metadata.")?; - - Ok(key_id) + let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type) + .context("Trying to rebind alias.")? + || need_gc; + Ok(key_id).do_gc(need_gc) }) .context(err!()) } @@ -1057,10 +1275,18 @@ impl KeymasterDb { let _wp = wd::watch("KeystoreDB::store_new_certificate"); let (alias, domain, namespace) = match key { - KeyDescriptor { alias: Some(alias), domain: Domain::APP, nspace, blob: None } - | KeyDescriptor { alias: Some(alias), domain: Domain::SELINUX, nspace, blob: None } => { - (alias, key.domain, nspace) + KeyDescriptor { + alias: Some(alias), + domain: Domain::APP, + nspace, + blob: None, } + | KeyDescriptor { + alias: Some(alias), + domain: Domain::SELINUX, + nspace, + blob: None, + } => (alias, key.domain, nspace), _ => { return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) .context(err!("Need alias and domain must be APP or SELINUX.")); @@ -1084,16 +1310,17 @@ impl KeymasterDb { DateTime::now().context("Trying to make creation time.")?, )); - metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?; + metadata + .store_in_db(key_id.id(), tx) + .context("Trying to insert key metadata.")?; let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type) .context("Trying to rebind alias.")?; - Ok(key_id) + Ok(key_id).do_gc(need_gc) }) .context(err!()) } - /// Loads super key of a given user, if exists pub fn load_super_key( &mut self, @@ -1121,6 +1348,7 @@ impl KeymasterDb { _ => Err(error).context(err!()), }, } + .no_gc() }) .context(err!()) } @@ -1170,6 +1398,7 @@ impl KeymasterDb { Self::load_key_components(tx, KeyEntryLoadBits::KM, key_id) .context("Trying to load key components.") + .no_gc() }) .context(err!()) } @@ -1248,7 +1477,7 @@ impl KeymasterDb { let _wp = wd::watch("KeystoreDB::set_blob"); self.with_transaction(Immediate("TX_set_blob"), |tx| { - Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata) + Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata).need_gc() }) .context(err!()) } @@ -1271,11 +1500,6 @@ impl KeymasterDb { let _wp = wd::watch("KeystoreDB::unbind_keys_for_user"); self.with_transaction(Immediate("TX_unbind_keys_for_user"), |tx| { - Self::delete_received_grants(tx, user_id).context(format!( - "In unbind_keys_for_user. Failed to delete received grants for user ID {:?}.", - user_id - ))?; - let mut stmt = tx .prepare(&format!( "SELECT id from persistent.keyentry @@ -1320,7 +1544,13 @@ impl KeymasterDb { }) .context(err!())?; - Ok(()) + let mut notify_gc = false; + for key_id in key_ids { + notify_gc = Self::mark_unreferenced(tx, key_id) + .context("In unbind_keys_for_user.")? + || notify_gc; + } + Ok(()).do_gc(notify_gc) }) .context(err!()) } @@ -1358,6 +1588,7 @@ impl KeymasterDb { ) .optional() .context("Trying to load key descriptor") + .no_gc() }) .context(err!()) } @@ -1378,6 +1609,7 @@ impl KeymasterDb { storage_type.0 ) }) + .no_gc() })?; Ok(StorageStats { storage_type, @@ -1438,7 +1670,11 @@ impl KeymasterDb { {} ORDER BY alias ASC LIMIT 10000;", - if start_past_alias.is_some() { " AND alias > ?" } else { "" } + if start_past_alias.is_some() { + " AND alias > ?" + } else { + "" + } ); self.with_transaction(TransactionBehavior::Deferred, |tx| { @@ -1455,7 +1691,12 @@ impl KeymasterDb { ]) .context(err!("Failed to query."))?, None => stmt - .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type,]) + .query(params![ + domain.0 as u32, + namespace, + KeyLifeCycle::Live, + key_type, + ]) .context(err!("Failed to query."))?, }; @@ -1470,11 +1711,10 @@ impl KeymasterDb { Ok(()) }) .context(err!("Failed to extract rows."))?; - Ok(descriptors) + Ok(descriptors).no_gc() }) } - /// Returns a number of KeyDescriptors in the selected domain/namespace. /// Domain must be APP or SELINUX, the caller must make sure of that. pub fn count_keys( @@ -1497,11 +1737,11 @@ impl KeymasterDb { |row| row.get(0), ) .context(err!("Failed to count number of keys.")) + .no_gc() })?; Ok(num_keys) } - /// Adds a grant to the grant table. /// Like `load_key_entry` this function loads the access tuple before /// it uses the callback for a permission check. Upon success, @@ -1567,7 +1807,13 @@ impl KeymasterDb { .context(err!())? }; - Ok(KeyDescriptor { domain: Domain::GRANT, nspace: grant_id, alias: None, blob: None }) + Ok(KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_id, + alias: None, + blob: None, + }) + .no_gc() }) } @@ -1598,7 +1844,7 @@ impl KeymasterDb { ) .context("Failed to delete grant.")?; - Ok(()) + Ok(()).no_gc() }) } @@ -1612,7 +1858,7 @@ impl KeymasterDb { ) -> Result<()> { let _wp = wd::watch("KeystoreDB::unbind_key"); - let _result = self.with_transaction(Immediate("TX_unbind_key"), |tx| { + self.with_transaction(Immediate("TX_unbind_key"), |tx| { let access = Self::load_access_tuple(tx, key, key_type, caller_uid) .context("Trying to get access tuple.")?; @@ -1622,11 +1868,10 @@ impl KeymasterDb { // .context("While checking permission.")?; Self::mark_unreferenced(tx, access.key_id) + .map(|need_gc| (need_gc, ())) .context("Trying to mark the key unreferenced.") }) - .context(err!())?; - - Ok(()) + .context(err!()) } /// Delete all artifacts belonging to the namespace given by the domain-namespace tuple. @@ -1671,12 +1916,11 @@ impl KeymasterDb { params![domain.0, namespace, KeyType::Client], ) .context("Trying to delete keyentry.")?; - Ok(()) + Ok(()).need_gc() }) .context(err!()) } - /// Fetches a storage statistics atom for a given storage type. For storage /// types that map to a table, information about the table's storage is /// returned. Requests for storage types that are not DB tables return None. @@ -1769,13 +2013,11 @@ impl KeymasterDb { .context("Failed to update key usage count.")?; match limit { - 1 => { - Self::mark_unreferenced(tx, key_id) - .context("Trying to mark limited use key for deletion.")?; - Ok(()) - } + 1 => Self::mark_unreferenced(tx, key_id) + .map(|need_gc| (need_gc, ())) + .context("Trying to mark limited use key for deletion."), 0 => Err(KsError::Km(ErrorCode::INVALID_KEY_BLOB)).context("Key is exhausted."), - _ => Ok(()), + _ => Ok(()).no_gc(), } }) .context(err!()) @@ -2084,7 +2326,7 @@ impl BlobMetaData { /// Right now it can only be initialized from SecurityLevel. /// Once KeyMint provides a UUID type a corresponding From impl shall be added. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Uuid([u8; 16]); +pub struct Uuid(pub(crate) [u8; 16]); impl From for Uuid { fn from(sec_level: SecurityLevel) -> Self { diff --git a/src/keymaster/gc.rs b/src/keymaster/gc.rs new file mode 100644 index 0000000..363b66c --- /dev/null +++ b/src/keymaster/gc.rs @@ -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. + +//! This module implements the key garbage collector. +//! The key garbage collector has one public function `notify_gc()`. This will create +//! a thread on demand which will query the database for unreferenced key entries, +//! optionally dispose of sensitive key material appropriately, and then delete +//! the key entry from the database. + +use crate::err; +use crate::keymaster::db::SupersededBlob; +use crate::keymaster::{ + async_task, + db::{KeymasterDb, Uuid}, + super_key::SuperKeyManager, +}; +use anyhow::{Context, Result}; +use async_task::AsyncTask; +use std::sync::{ + atomic::{AtomicU8, Ordering}, + Arc, RwLock, +}; + +pub struct Gc { + async_task: Arc, + notified: Arc, +} + +impl Gc { + /// Creates a garbage collector using the given async_task. + /// The garbage collector needs a function to invalidate key blobs, a database connection, + /// and a reference to the `SuperKeyManager`. They are obtained from the init function. + /// The function is only called if this is first time a garbage collector was initialized + /// with the given AsyncTask instance. + /// Note: It is a logical error to initialize different Gc instances with the same `AsyncTask`. + pub fn new_init_with(async_task: Arc, init: F) -> Self + where + F: FnOnce() -> ( + Box Result<()> + Send + 'static>, + KeymasterDb, + Arc>, + ) + Send + + 'static, + { + let weak_at = Arc::downgrade(&async_task); + let notified = Arc::new(AtomicU8::new(0)); + let notified_clone = notified.clone(); + // Initialize the task's shelf. + async_task.queue_hi(move |shelf| { + let (invalidate_key, db, super_key) = init(); + let notified = notified_clone; + shelf.get_or_put_with(|| GcInternal { + deleted_blob_ids: vec![], + superseded_blobs: vec![], + invalidate_key, + db, + async_task: weak_at, + super_key, + notified, + }); + }); + Self { async_task, notified } + } + + /// Notifies the key garbage collector to iterate through orphaned and superseded blobs and + /// attempts their deletion. We only process one key at a time and then schedule another + /// attempt by queueing it in the async_task (low priority) queue. + pub fn notify_gc(&self) { + if let Ok(0) = self.notified.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) { + self.async_task.queue_lo(|shelf| shelf.get_downcast_mut::().unwrap().step()) + } + } +} + +struct GcInternal { + deleted_blob_ids: Vec, + superseded_blobs: Vec, + invalidate_key: Box Result<()> + Send + 'static>, + db: KeymasterDb, + async_task: std::sync::Weak, + super_key: Arc>, + notified: Arc, +} + +impl GcInternal { + /// Attempts to process one blob from the database. + /// We process one key at a time, because deleting a key is a time consuming process which + /// may involve calling into the KeyMint backend and we don't want to hog neither the backend + /// nor the database for extended periods of time. + /// To limit the number of database transactions, which are also expensive and competing + /// with threads on the critical path, deleted blobs are loaded in batches. + fn process_one_key(&mut self) -> Result<()> { + if self.superseded_blobs.is_empty() { + let blobs = self + .db + .handle_next_superseded_blobs(&self.deleted_blob_ids, 20) + .context(err!("Trying to handle superseded blob."))?; + self.deleted_blob_ids = vec![]; + self.superseded_blobs = blobs; + } + + if let Some(SupersededBlob { blob_id, blob, metadata }) = self.superseded_blobs.pop() { + // Add the next blob_id to the deleted blob ids list. So it will be + // removed from the database regardless of whether the following + // succeeds or not. + self.deleted_blob_ids.push(blob_id); + + // If the key has a km_uuid we try to get the corresponding device + // and delete the key, unwrapping if necessary and possible. + // (At this time keys may get deleted without having the super encryption + // key in this case we can only delete the key from the database.) + if let Some(uuid) = metadata.km_uuid() { + let blob = self + .super_key + .read() + .unwrap() + .unwrap_key_if_required(&metadata, &blob) + .context(err!("Trying to unwrap to-be-deleted blob.",))?; + (self.invalidate_key)(uuid, &blob).context(err!("Trying to invalidate key."))?; + } + } + Ok(()) + } + + /// Processes one key and then schedules another attempt until it runs out of blobs to delete. + fn step(&mut self) { + self.notified.store(0, Ordering::Relaxed); + + if let Err(e) = self.process_one_key() { + log::error!("Error trying to delete blob entry. {:?}", e); + } + // Schedule the next step. This gives high priority requests a chance to interleave. + if !self.deleted_blob_ids.is_empty() { + if let Some(at) = self.async_task.upgrade() { + if let Ok(0) = + self.notified.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) + { + at.queue_lo(move |shelf| { + shelf.get_downcast_mut::().unwrap().step() + }); + } + } + } + } +} diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index 1eaa2a3..f25d556 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -1126,6 +1126,25 @@ impl KeyMintWrapper { Result::Ok(()) } + + #[allow(non_snake_case)] + pub fn delete_Key(&self, key_blob: &[u8]) -> Result<(), Error> { + let req = PerformOpReq::DeviceDeleteKey(DeleteKeyRequest { + key_blob: key_blob.to_vec(), + }); + let result = get_keymint_device(self.security_level) + .unwrap() + .process_req(req); + + if result.error_code != 0 { + return Err(Error::Binder( + ExceptionCode::ServiceSpecific, + result.error_code, + )); + } + + Result::Ok(()) + } } pub fn get_keymint_security_level( diff --git a/src/keymaster/mod.rs b/src/keymaster/mod.rs index 824d053..027a6e2 100644 --- a/src/keymaster/mod.rs +++ b/src/keymaster/mod.rs @@ -1,11 +1,13 @@ pub mod apex; pub mod attestation_key_utils; +pub mod async_task; pub mod boot_key; pub mod crypto; pub mod database; pub mod db; pub mod enforcements; pub mod error; +pub mod gc; pub mod key_parameter; pub mod keymint_device; pub mod keymint_operation; diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 177d46a..e6eb3d8 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -30,7 +30,7 @@ use crate::{ metrics_store::log_key_creation_event_stats, operation::{KeystoreOperation, LoggingInfo, OperationDb}, super_key::{KeyBlob, SuperKeyManager}, - utils::{key_characteristics_to_internal, key_param_to_aidl, key_parameters_to_authorizations, log_params}, + utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, }, plat::utils::multiuser_get_user_id, }; @@ -41,7 +41,7 @@ use crate::keymaster::key_parameter::KeyParameterValue as KsKeyParamValue; use crate::watchdog as wd; use anyhow::{anyhow, Context, Result}; -use kmr_wire::keymint::{KeyMintHardwareInfo, KeyParam}; +use kmr_wire::keymint::KeyMintHardwareInfo; use log::debug; use rsbinder::{thread_state::CallingContext, Interface, Status}; diff --git a/src/keymaster/service.rs b/src/keymaster/service.rs index 6c064cb..302f3f9 100644 --- a/src/keymaster/service.rs +++ b/src/keymaster/service.rs @@ -140,6 +140,8 @@ impl KeystoreService { fn get_key_entry(&self, key: &KeyDescriptor) -> Result { let caller_uid = CallingContext::default().uid; + debug!("get_key_entry: key={:?}, uid={}", key, caller_uid); + let super_key = SUPER_KEY .read() .unwrap() From 794a6745dc85485b5aa98fc20dc26d63bfa89b0c Mon Sep 17 00:00:00 2001 From: qwq233 Date: Tue, 14 Oct 2025 02:43:36 +0800 Subject: [PATCH 14/46] fix dead lock Signed-off-by: qwq233 --- .idea/.gitignore | 10 + .idea/OhMyKeymint.iml | 26 ++ .idea/modules.xml | 8 + .idea/vcs.xml | 6 + aidl/top/qwq2333/ohmykeymint/CallerInfo.aidl | 7 + .../qwq2333/ohmykeymint/IOhMyKsService.aidl | 46 +++ .../ohmykeymint/IOhMySecurityLevel.aidl | 33 ++ build.rs | 1 + src/keymaster/security_level.rs | 138 +++++++- src/keymaster/service.rs | 306 ++++++++++++++---- src/main.rs | 41 +-- src/plat/mod.rs | 1 + src/plat/pm.rs | 231 +++++++++++++ src/plat/utils.rs | 55 ++-- 14 files changed, 792 insertions(+), 117 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/OhMyKeymint.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 aidl/top/qwq2333/ohmykeymint/CallerInfo.aidl create mode 100644 aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl create mode 100644 aidl/top/qwq2333/ohmykeymint/IOhMySecurityLevel.aidl create mode 100644 src/plat/pm.rs diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/OhMyKeymint.iml b/.idea/OhMyKeymint.iml new file mode 100644 index 0000000..7ad7ca0 --- /dev/null +++ b/.idea/OhMyKeymint.iml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..3b2f3d6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/aidl/top/qwq2333/ohmykeymint/CallerInfo.aidl b/aidl/top/qwq2333/ohmykeymint/CallerInfo.aidl new file mode 100644 index 0000000..ddd5fd8 --- /dev/null +++ b/aidl/top/qwq2333/ohmykeymint/CallerInfo.aidl @@ -0,0 +1,7 @@ +package top.qwq2333.ohmykeymint; + +parcelable CallerInfo { + long callingUid; + String callingSid; + long callingPid; +} \ No newline at end of file diff --git a/aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl b/aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl new file mode 100644 index 0000000..433bb88 --- /dev/null +++ b/aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl @@ -0,0 +1,46 @@ +package top.qwq2333.ohmykeymint; + +import android.hardware.security.keymint.SecurityLevel; +import android.hardware.security.keymint.Tag; +import android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; + +import top.qwq2333.ohmykeymint.IOhMySecurityLevel; +import top.qwq2333.ohmykeymint.CallerInfo; + +interface IOhMyKsService { + + IKeystoreSecurityLevel getSecurityLevel(in SecurityLevel securityLevel); + + IOhMySecurityLevel getOhMySecurityLevel(in SecurityLevel securityLevel); + + KeyEntryResponse getKeyEntry(in @nullable CallerInfo ctx, in KeyDescriptor key); + + + void updateSubcomponent(in @nullable CallerInfo ctx, in KeyDescriptor key, + in @nullable byte[] publicCert, in @nullable byte[] certificateChain); + + + KeyDescriptor[] listEntries(in @nullable CallerInfo ctx, in Domain domain, in long nspace); + + + void deleteKey(in @nullable CallerInfo ctx, in KeyDescriptor key); + + + KeyDescriptor grant(in @nullable CallerInfo ctx, in KeyDescriptor key, in int granteeUid, in int accessVector); + + + void ungrant(in @nullable CallerInfo ctx, in KeyDescriptor key, in int granteeUid); + + + int getNumberOfEntries(in @nullable CallerInfo ctx, in Domain domain, in long nspace); + + + KeyDescriptor[] listEntriesBatched(in @nullable CallerInfo ctx, in Domain domain, in long nspace, + in @nullable String startingPastAlias); + + + byte[] getSupplementaryAttestationInfo(in Tag tag); +} \ No newline at end of file diff --git a/aidl/top/qwq2333/ohmykeymint/IOhMySecurityLevel.aidl b/aidl/top/qwq2333/ohmykeymint/IOhMySecurityLevel.aidl new file mode 100644 index 0000000..0c02f76 --- /dev/null +++ b/aidl/top/qwq2333/ohmykeymint/IOhMySecurityLevel.aidl @@ -0,0 +1,33 @@ +package top.qwq2333.ohmykeymint; + +import android.hardware.security.keymint.KeyParameter; +import android.system.keystore2.AuthenticatorSpec; +import android.system.keystore2.CreateOperationResponse; +import android.system.keystore2.EphemeralStorageKeyResponse; +import android.system.keystore2.IKeystoreOperation; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; + +import top.qwq2333.ohmykeymint.CallerInfo; + +interface IOhMySecurityLevel { + + const int KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING = 0x1; + + CreateOperationResponse createOperation(in @nullable CallerInfo ctx, in KeyDescriptor key, + in KeyParameter[] operationParameters, in boolean forced); + + KeyMetadata generateKey(in @nullable CallerInfo ctx, in KeyDescriptor key, in @nullable KeyDescriptor attestationKey, + in KeyParameter[] params, in int flags, in byte[] entropy); + + KeyMetadata importKey(in @nullable CallerInfo ctx, in KeyDescriptor key, in @nullable KeyDescriptor attestationKey, + in KeyParameter[] params, in int flags, in byte[] keyData); + + KeyMetadata importWrappedKey(in @nullable CallerInfo ctx, in KeyDescriptor key, in KeyDescriptor wrappingKey, + in @nullable byte[] maskingKey, in KeyParameter[] params, + in AuthenticatorSpec[] authenticators); + + EphemeralStorageKeyResponse convertStorageKeyToEphemeral(in KeyDescriptor storageKey); + + void deleteKey(in KeyDescriptor key); +} diff --git a/build.rs b/build.rs index 204d81d..b9b6987 100644 --- a/build.rs +++ b/build.rs @@ -33,6 +33,7 @@ fn main() { "aidl/android/hardware/security/keymint", "aidl/android/security/metrics", "aidl/android/apex", + "aidl/top/qwq2333/ohmykeymint", ]; for dir in dirs { println!("Processing AIDL files in directory: {}", dir); diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index e6eb3d8..00f6667 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -32,7 +32,7 @@ use crate::{ super_key::{KeyBlob, SuperKeyManager}, utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, }, - plat::utils::multiuser_get_user_id, + plat::utils::multiuser_get_user_id, top::qwq2333::ohmykeymint::{CallerInfo::CallerInfo, IOhMySecurityLevel::IOhMySecurityLevel}, }; use crate::keymaster::key_parameter::KeyParameter as KsKeyParam; @@ -377,6 +377,7 @@ impl KeystoreSecurityLevel { #[allow(unused_variables)] pub fn generate_key( &self, + ctx: Option<&CallerInfo>, key: &KeyDescriptor, attest_key_descriptor: Option<&KeyDescriptor>, params: &[KeyParameter], @@ -387,8 +388,11 @@ impl KeystoreSecurityLevel { return Err(KsError::Km(ErrorCode::INVALID_ARGUMENT)) .context(err!("Alias must be provided for non-BLOB domains")); } - let calling_context = CallingContext::default(); - let calling_uid = calling_context.uid; + let calling_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; debug!("KeystoreSecurityLevel::generate_key: uid={:?} key={:?}, attest_key_descriptor={:?}, params={:?}, flags={}", calling_uid, key, attest_key_descriptor, log_params(params), flags); @@ -487,6 +491,7 @@ impl KeystoreSecurityLevel { fn import_key( &self, + ctx: Option<&CallerInfo>, key: &KeyDescriptor, _attestation_key: Option<&KeyDescriptor>, params: &[KeyParameter], @@ -498,8 +503,11 @@ impl KeystoreSecurityLevel { .context(err!("Alias must be specified")); } - let calling_context = rsbinder::thread_state::CallingContext::default(); - let caller_uid = calling_context.uid; + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; let key = match key.domain { Domain::APP => KeyDescriptor { @@ -549,6 +557,7 @@ impl KeystoreSecurityLevel { fn import_wrapped_key( &self, + ctx: Option<&CallerInfo>, key: &KeyDescriptor, wrapping_key: &KeyDescriptor, masking_key: Option<&[u8]>, @@ -582,8 +591,11 @@ impl KeystoreSecurityLevel { )); } - let calling_context = rsbinder::thread_state::CallingContext::default(); - let caller_uid = calling_context.uid; + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; let user_id = multiuser_get_user_id(caller_uid); let key = match key.domain { @@ -740,11 +752,16 @@ impl KeystoreSecurityLevel { fn create_operation( &self, + ctx: Option<&CallerInfo>, key: &KeyDescriptor, operation_parameters: &[KeyParameter], forced: bool, ) -> Result { - let caller_uid = CallingContext::default().uid; + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; // We use `scoping_blob` to extend the life cycle of the blob loaded from the database, // so that we can use it by reference like the blob provided by the key descriptor. // Otherwise, we would have to clone the blob from the key descriptor. @@ -977,7 +994,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { ) -> Result { let _wp = self.watch("IKeystoreSecurityLevel::createOperation"); Ok(self - .create_operation(key, operation_parameters, forced) + .create_operation(None, key, operation_parameters, forced) .map_err(into_logged_binder)?) } @@ -992,10 +1009,10 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { // Duration is set to 5 seconds, because generateKey - especially for RSA keys, takes more // time than other operations let _wp = self.watch_millis("IKeystoreSecurityLevel::generateKey", 5000); - let result = self.generate_key(key, attestation_key, params, flags, entropy); + let result = self.generate_key(None, key, attestation_key, params, flags, entropy); log_key_creation_event_stats(self.security_level, params, &result); debug!( - "generateKey: calling uid: {}, result: {:?}", + "generateKey: calling uid: {}, result: {:02x?}", CallingContext::default().uid, result ); @@ -1011,7 +1028,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { key_data: &[u8], ) -> Result { let _wp = self.watch("IKeystoreSecurityLevel::importKey"); - let result = self.import_key(key, attestation_key, params, flags, key_data); + let result = self.import_key(None, key, attestation_key, params, flags, key_data); log_key_creation_event_stats(self.security_level, params, &result); debug!( "importKey: calling uid: {}, result: {:?}", @@ -1030,7 +1047,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { ) -> Result { let _wp = self.watch("IKeystoreSecurityLevel::importWrappedKey"); let result = - self.import_wrapped_key(key, wrapping_key, masking_key, params, authenticators); + self.import_wrapped_key(None, key, wrapping_key, masking_key, params, authenticators); log_key_creation_event_stats(self.security_level, params, &result); debug!( "importWrappedKey: calling uid: {}, result: {:?}", @@ -1058,3 +1075,98 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { result.map_err(into_logged_binder) } } + +impl IOhMySecurityLevel for KeystoreSecurityLevel { + fn createOperation( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + operation_parameters: &[KeyParameter], + forced: bool, + ) -> Result { + let _wp = self.watch("IOhMySecurityLevel::createOperation"); + Ok(self + .create_operation(ctx, key, operation_parameters, forced) + .map_err(into_logged_binder)?) + } + + fn generateKey( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + attestation_key: Option<&KeyDescriptor>, + params: &[KeyParameter], + flags: i32, + entropy: &[u8], + ) -> Result { + // Duration is set to 5 seconds, because generateKey - especially for RSA keys, takes more + // time than other operations + let _wp = self.watch_millis("IOhMySecurityLevel::generateKey", 5000); + let result = self.generate_key(ctx, key, attestation_key, params, flags, entropy); + log_key_creation_event_stats(self.security_level, params, &result); + debug!( + "generateKey: calling uid: {}, result: {:02x?}", + ctx.is_some().then(|| ctx.unwrap().callingUid).unwrap_or(CallingContext::default().uid.into()), + result + ); + result.map_err(into_logged_binder) + } + + fn importKey( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + attestation_key: Option<&KeyDescriptor>, + params: &[KeyParameter], + flags: i32, + key_data: &[u8], + ) -> Result { + let _wp = self.watch("IOhMySecurityLevel::importKey"); + let result = self.import_key(ctx, key, attestation_key, params, flags, key_data); + log_key_creation_event_stats(self.security_level, params, &result); + debug!( + "importKey: calling uid: {}, result: {:?}", + ctx.is_some().then(|| ctx.unwrap().callingUid).unwrap_or(CallingContext::default().uid.into()), + result + ); + result.map_err(into_logged_binder) + } + fn importWrappedKey( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + wrapping_key: &KeyDescriptor, + masking_key: Option<&[u8]>, + params: &[KeyParameter], + authenticators: &[AuthenticatorSpec], + ) -> Result { + let _wp = self.watch("IOhMySecurityLevel::importWrappedKey"); + let result = + self.import_wrapped_key(ctx, key, wrapping_key, masking_key, params, authenticators); + log_key_creation_event_stats(self.security_level, params, &result); + debug!( + "importWrappedKey: calling uid: {}, result: {:?}", + ctx.is_some().then(|| ctx.unwrap().callingUid).unwrap_or(CallingContext::default().uid.into()), + result + ); + result.map_err(into_logged_binder) + } + fn convertStorageKeyToEphemeral( + &self, + storage_key: &KeyDescriptor, + ) -> Result { + let _wp = self.watch("IOhMySecurityLevel::convertStorageKeyToEphemeral"); + self.convert_storage_key_to_ephemeral(storage_key) + .map_err(into_logged_binder) + } + fn deleteKey(&self, key: &KeyDescriptor) -> Result<(), Status> { + let _wp = self.watch("IOhMySecurityLevel::deleteKey"); + let result = self.delete_key(key); + debug!( + "deleteKey: calling uid: {}, result: {:?}", + CallingContext::default().uid, + result + ); + result.map_err(into_logged_binder) + } +} diff --git a/src/keymaster/service.rs b/src/keymaster/service.rs index 302f3f9..2e2cddb 100644 --- a/src/keymaster/service.rs +++ b/src/keymaster/service.rs @@ -31,6 +31,11 @@ use crate::keymaster::permission::KeyPermSet; use crate::keymaster::security_level::KeystoreSecurityLevel; use crate::keymaster::utils::key_parameters_to_authorizations; use crate::plat::utils::multiuser_get_user_id; +use crate::top::qwq2333::ohmykeymint::CallerInfo::CallerInfo; +use crate::top::qwq2333::ohmykeymint::IOhMyKsService::IOhMyKsService; +use crate::top::qwq2333::ohmykeymint::IOhMySecurityLevel::{ + BnOhMySecurityLevel, IOhMySecurityLevel, +}; use crate::watchdog as wd; use crate::{ global::{DB, SUPER_KEY}, @@ -45,35 +50,32 @@ use crate::android::system::keystore2::{ KeyDescriptor::KeyDescriptor, KeyEntryResponse::KeyEntryResponse, KeyMetadata::KeyMetadata, }; use anyhow::{Context, Ok, Result}; -use der::Encode; use der::asn1::SetOfVec; -use kmr_crypto_boring::sha256::BoringSha256; +use der::Encode; use kmr_common::crypto::Sha256; +use kmr_crypto_boring::sha256::BoringSha256; use log::debug; use rsbinder::thread_state::CallingContext; use rsbinder::{Status, Strong}; - fn encode_module_info(module_info: Vec) -> Result, der::Error> { SetOfVec::::from_iter(module_info.into_iter())?.to_der() } /// Implementation of the IKeystoreService. -#[derive(Default)] +#[derive(Default, Clone)] pub struct KeystoreService { i_sec_level_by_uuid: HashMap>, + i_osec_level_by_uuid: HashMap>, uuid_by_sec_level: HashMap, } impl KeystoreService { /// Create a new instance of the Keystore 2.0 service. - pub fn new_native_binder( - ) -> Result> { + pub fn new_native_binder() -> Result { let mut result: Self = Default::default(); - let (dev, uuid) = match KeystoreSecurityLevel::new( - SecurityLevel::TRUSTED_ENVIRONMENT, - ) { + let (dev, uuid) = match KeystoreSecurityLevel::new(SecurityLevel::TRUSTED_ENVIRONMENT) { Result::Ok(v) => v, Err(e) => { log::error!("Failed to construct mandatory security level TEE: {e:?}"); @@ -90,9 +92,7 @@ impl KeystoreService { .insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid); // Strongbox is optional, so we ignore errors and turn the result into an Option. - if let Result::Ok((dev, uuid)) = - KeystoreSecurityLevel::new(SecurityLevel::STRONGBOX) - { + if let Result::Ok((dev, uuid)) = KeystoreSecurityLevel::new(SecurityLevel::STRONGBOX) { let dev: Strong = BnKeystoreSecurityLevel::new_binder(dev); result.i_sec_level_by_uuid.insert(uuid, dev); result @@ -100,9 +100,25 @@ impl KeystoreService { .insert(SecurityLevel::STRONGBOX, uuid); } - Ok(BnKeystoreService::new_binder( - result - )) + // OhMyKeymint extension: register the same security levels also as IOhMySecurityLevel. + let (dev, _uuid) = match KeystoreSecurityLevel::new(SecurityLevel::TRUSTED_ENVIRONMENT) { + Result::Ok(v) => v, + Err(e) => { + log::error!("Failed to construct mandatory security level TEE: {e:?}"); + log::error!("Does the device have a /default Keymaster or KeyMint instance?"); + return Err(e.context(err!("Trying to construct mandatory security level TEE"))); + } + }; + + let dev: Strong = BnOhMySecurityLevel::new_binder(dev); + result.i_osec_level_by_uuid.insert(uuid, dev); + + if let Result::Ok((dev, uuid)) = KeystoreSecurityLevel::new(SecurityLevel::STRONGBOX) { + let dev: Strong = BnOhMySecurityLevel::new_binder(dev); + result.i_osec_level_by_uuid.insert(uuid, dev); + } + + Ok(result) } fn uuid_to_sec_level(&self, uuid: &Uuid) -> SecurityLevel { @@ -123,6 +139,7 @@ impl KeystoreService { fn get_security_level( &self, + _ctx: Option<&CallerInfo>, // reserved for future use sec_level: SecurityLevel, ) -> Result> { if let Some(dev) = self @@ -137,8 +154,33 @@ impl KeystoreService { } } - fn get_key_entry(&self, key: &KeyDescriptor) -> Result { - let caller_uid = CallingContext::default().uid; + fn get_iohmy_security_level( + &self, + _ctx: Option<&CallerInfo>, // reserved for future use + sec_level: SecurityLevel, + ) -> Result> { + if let Some(dev) = self + .uuid_by_sec_level + .get(&sec_level) + .and_then(|uuid| self.i_osec_level_by_uuid.get(uuid)) + { + Ok(dev.clone()) + } else { + Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context(err!("No such security level.")) + } + } + + pub fn get_key_entry( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + ) -> Result { + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; debug!("get_key_entry: key={:?}, uid={}", key, caller_uid); @@ -191,11 +233,16 @@ impl KeystoreService { fn update_subcomponent( &self, + ctx: Option<&CallerInfo>, key: &KeyDescriptor, public_cert: Option<&[u8]>, certificate_chain: Option<&[u8]>, ) -> Result<()> { - let caller_uid = CallingContext::default().uid; + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; let _super_key = SUPER_KEY .read() .unwrap() @@ -242,7 +289,7 @@ impl KeystoreService { let key = match (key.domain, &key.alias) { (Domain::APP, Some(ref alias)) => KeyDescriptor { domain: Domain::APP, - nspace: CallingContext::default().uid as i64, + nspace: caller_uid as i64, alias: Some(alias.clone()), blob: None, }, @@ -272,13 +319,20 @@ impl KeystoreService { fn get_key_descriptor_for_lookup( &self, + ctx: Option<&CallerInfo>, domain: Domain, namespace: i64, ) -> Result { + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + }; + let k = match domain { Domain::APP => KeyDescriptor { domain, - nspace: CallingContext::default().uid as u64 as i64, + nspace: caller_uid, ..Default::default() }, Domain::SELINUX => KeyDescriptor { @@ -314,14 +368,24 @@ impl KeystoreService { Ok(k) } - fn list_entries(&self, domain: Domain, namespace: i64) -> Result> { - let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + fn list_entries( + &self, + ctx: Option<&CallerInfo>, + domain: Domain, + namespace: i64, + ) -> Result> { + let k = self.get_key_descriptor_for_lookup(ctx, domain, namespace)?; DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, None)) } - fn count_num_entries(&self, domain: Domain, namespace: i64) -> Result { - let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + fn count_num_entries( + &self, + ctx: Option<&CallerInfo>, + domain: Domain, + namespace: i64, + ) -> Result { + let k = self.get_key_descriptor_for_lookup(ctx, domain, namespace)?; DB.with(|db| count_key_entries(&mut db.borrow_mut(), k.domain, k.nspace)) } @@ -329,18 +393,21 @@ impl KeystoreService { fn get_supplementary_attestation_info(&self, tag: Tag) -> Result> { match tag { Tag::MODULE_HASH => { - let info = ENCODED_MODULE_INFO.get_or_try_init(|| -> Result, anyhow::Error> { - let apex_info = crate::plat::utils::get_apex_module_info()?; + let info = + ENCODED_MODULE_INFO.get_or_try_init(|| -> Result, anyhow::Error> { + let apex_info = crate::plat::utils::get_apex_module_info()?; - let encoding = encode_module_info(apex_info) - .map_err(|_| anyhow::anyhow!("Failed to encode module info."))?; + let encoding = encode_module_info(apex_info) + .map_err(|_| anyhow::anyhow!("Failed to encode module info."))?; - let sha256 = BoringSha256 {}; + let sha256 = BoringSha256 {}; - let hash = sha256.hash(&encoding).map_err(|_| anyhow::anyhow!("Failed to hash module info."))?; + let hash = sha256 + .hash(&encoding) + .map_err(|_| anyhow::anyhow!("Failed to hash module info."))?; - Ok(hash.to_vec()) - })?; + Ok(hash.to_vec()) + })?; Ok(info.clone()) } @@ -352,16 +419,21 @@ impl KeystoreService { fn list_entries_batched( &self, + ctx: Option<&CallerInfo>, domain: Domain, namespace: i64, start_past_alias: Option<&str>, ) -> Result> { - let k = self.get_key_descriptor_for_lookup(domain, namespace)?; + let k = self.get_key_descriptor_for_lookup(ctx, domain, namespace)?; DB.with(|db| list_key_entries(&mut db.borrow_mut(), k.domain, k.nspace, start_past_alias)) } - fn delete_key(&self, key: &KeyDescriptor) -> Result<()> { - let caller_uid = CallingContext::default().uid; + fn delete_key(&self, ctx: Option<&CallerInfo>, key: &KeyDescriptor) -> Result<()> { + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; let super_key = SUPER_KEY .read() .unwrap() @@ -374,11 +446,16 @@ impl KeystoreService { fn grant( &self, + ctx: Option<&CallerInfo>, key: &KeyDescriptor, grantee_uid: i32, access_vector: KeyPermSet, ) -> Result { - let caller_uid = CallingContext::default().uid; + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; let super_key = SUPER_KEY .read() .unwrap() @@ -391,12 +468,19 @@ impl KeystoreService { .context(err!("KeystoreService::grant.")) } - fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<()> { - DB.with(|db| { - db.borrow_mut() - .ungrant(key, CallingContext::default().uid, grantee_uid as u32) - }) - .context(err!("KeystoreService::ungrant.")) + fn ungrant( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + grantee_uid: i32, + ) -> Result<()> { + let caller_uid = if let Some(ctx) = ctx { + ctx.callingUid + } else { + CallingContext::default().uid.into() + } as u32; + DB.with(|db| db.borrow_mut().ungrant(key, caller_uid, grantee_uid as u32)) + .context(err!("KeystoreService::ungrant.")) } } @@ -404,18 +488,19 @@ impl rsbinder::Interface for KeystoreService {} // Implementation of IKeystoreService. See AIDL spec at // system/security/keystore2/binder/android/security/keystore2/IKeystoreService.aidl +#[allow(non_snake_case)] impl IKeystoreService for KeystoreService { fn getSecurityLevel( &self, security_level: SecurityLevel, ) -> Result, Status> { let _wp = wd::watch_millis_with("IKeystoreService::getSecurityLevel", 500, security_level); - self.get_security_level(security_level) + self.get_security_level(None, security_level) .map_err(into_logged_binder) } fn getKeyEntry(&self, key: &KeyDescriptor) -> Result { let _wp = wd::watch("IKeystoreService::get_key_entry"); - self.get_key_entry(key).map_err(into_logged_binder) + self.get_key_entry(None, key).map_err(into_logged_binder) } fn updateSubcomponent( &self, @@ -424,17 +509,17 @@ impl IKeystoreService for KeystoreService { certificate_chain: Option<&[u8]>, ) -> Result<(), Status> { let _wp = wd::watch("IKeystoreService::updateSubcomponent"); - self.update_subcomponent(key, public_cert, certificate_chain) + self.update_subcomponent(None, key, public_cert, certificate_chain) .map_err(into_logged_binder) } fn listEntries(&self, domain: Domain, namespace: i64) -> Result, Status> { let _wp = wd::watch("IKeystoreService::listEntries"); - self.list_entries(domain, namespace) + self.list_entries(None, domain, namespace) .map_err(into_logged_binder) } fn deleteKey(&self, key: &KeyDescriptor) -> Result<(), Status> { let _wp = wd::watch("IKeystoreService::deleteKey"); - let result = self.delete_key(key); + let result = self.delete_key(None, key); debug!( "deleteKey: key={:?}, uid={}", key, @@ -449,13 +534,20 @@ impl IKeystoreService for KeystoreService { access_vector: i32, ) -> Result { let _wp = wd::watch("IKeystoreService::grant"); - self.grant(key, grantee_uid, access_vector.into()) + self.grant(None, key, grantee_uid, access_vector.into()) .map_err(into_logged_binder) } fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<(), Status> { let _wp = wd::watch("IKeystoreService::ungrant"); - self.ungrant(key, grantee_uid).map_err(into_logged_binder) + self.ungrant(None, key, grantee_uid) + .map_err(into_logged_binder) + } + fn getNumberOfEntries(&self, domain: Domain, namespace: i64) -> Result { + let _wp = wd::watch("IKeystoreService::getNumberOfEntries"); + self.count_num_entries(None, domain, namespace) + .map_err(into_logged_binder) } + fn listEntriesBatched( &self, domain: Domain, @@ -463,18 +555,124 @@ impl IKeystoreService for KeystoreService { start_past_alias: Option<&str>, ) -> Result, Status> { let _wp = wd::watch("IKeystoreService::listEntriesBatched"); - self.list_entries_batched(domain, namespace, start_past_alias) + self.list_entries_batched(None, domain, namespace, start_past_alias) .map_err(into_logged_binder) } - fn getNumberOfEntries(&self, domain: Domain, namespace: i64) -> Result { - let _wp = wd::watch("IKeystoreService::getNumberOfEntries"); - self.count_num_entries(domain, namespace) + fn getSupplementaryAttestationInfo(&self, tag: Tag) -> Result, Status> { + let _wp = wd::watch("IKeystoreService::getSupplementaryAttestationInfo"); + self.get_supplementary_attestation_info(tag).map_err(|e| { + log::error!("Failed to get supplementary attestation info: {}", e); + // pretend as it's not supported + Status::from(rsbinder::StatusCode::UnknownTransaction) + }) + } +} + +impl IOhMyKsService for KeystoreService { + fn getSecurityLevel( + &self, + security_level: SecurityLevel, + ) -> Result, Status> { + let _wp = wd::watch_millis_with("IOhMyKsService::getSecurityLevel", 500, security_level); + self.get_security_level(None, security_level) + .map_err(into_logged_binder) + } + fn getOhMySecurityLevel( + &self, + security_level: SecurityLevel, + ) -> Result, Status> { + let _wp = wd::watch_millis_with("IOhMyKsService::getSecurityLevel", 500, security_level); + self.get_iohmy_security_level(None, security_level) + .map_err(into_logged_binder) + } + fn getKeyEntry( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + ) -> Result { + let _wp = wd::watch("IOhMyKsService::get_key_entry"); + self.get_key_entry(ctx, key).map_err(into_logged_binder) + } + fn updateSubcomponent( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + public_cert: Option<&[u8]>, + certificate_chain: Option<&[u8]>, + ) -> Result<(), Status> { + let _wp = wd::watch("IOhMyKsService::updateSubcomponent"); + self.update_subcomponent(ctx, key, public_cert, certificate_chain) + .map_err(into_logged_binder) + } + fn listEntries( + &self, + ctx: Option<&CallerInfo>, + domain: Domain, + namespace: i64, + ) -> Result, Status> { + let _wp = wd::watch("IOhMyKsService::listEntries"); + self.list_entries(ctx, domain, namespace) + .map_err(into_logged_binder) + } + fn deleteKey(&self, ctx: Option<&CallerInfo>, key: &KeyDescriptor) -> Result<(), Status> { + let _wp = wd::watch("IOhMyKsService::deleteKey"); + let result = self.delete_key(ctx, key); + debug!( + "deleteKey: key={:?}, uid={}", + key, + ctx.is_some() + .then(|| ctx.unwrap().callingUid) + .unwrap_or(CallingContext::default().uid.into()) + ); + result.map_err(into_logged_binder) + } + fn grant( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + grantee_uid: i32, + access_vector: i32, + ) -> Result { + let _wp = wd::watch("IOhMyKsService::grant"); + self.grant(ctx, key, grantee_uid, access_vector.into()) + .map_err(into_logged_binder) + } + fn ungrant( + &self, + ctx: Option<&CallerInfo>, + key: &KeyDescriptor, + grantee_uid: i32, + ) -> Result<(), Status> { + let _wp = wd::watch("IOhMyKsService::ungrant"); + self.ungrant(ctx, key, grantee_uid) + .map_err(into_logged_binder) + } + fn getNumberOfEntries( + &self, + ctx: Option<&CallerInfo>, + domain: Domain, + namespace: i64, + ) -> Result { + let _wp = wd::watch("IOhMyKsService::getNumberOfEntries"); + self.count_num_entries(ctx, domain, namespace) + .map_err(into_logged_binder) + } + + fn listEntriesBatched( + &self, + ctx: Option<&CallerInfo>, + domain: Domain, + namespace: i64, + start_past_alias: Option<&str>, + ) -> Result, Status> { + let _wp = wd::watch("IOhMyKsService::listEntriesBatched"); + self.list_entries_batched(ctx, domain, namespace, start_past_alias) .map_err(into_logged_binder) } fn getSupplementaryAttestationInfo(&self, tag: Tag) -> Result, Status> { - let _wp = wd::watch("IKeystoreService::getSupplementaryAttestationInfo"); + let _wp = wd::watch("IOhMyKsService::getSupplementaryAttestationInfo"); self.get_supplementary_attestation_info(tag).map_err(|e| { log::error!("Failed to get supplementary attestation info: {}", e); // pretend as it's not supported diff --git a/src/main.rs b/src/main.rs index 268431f..df45ccf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ use log::{debug, error}; use rsbinder::hub; use crate::{ - android::system::keystore2:: - IKeystoreOperation, keymaster::service::KeystoreService} + android::system::keystore2::{ + IKeystoreOperation, IKeystoreService::BnKeystoreService}, keymaster::service::KeystoreService, top::qwq2333::ohmykeymint::IOhMyKsService::BnOhMyKsService} ; @@ -45,38 +45,17 @@ fn main() { rsbinder::ProcessState::start_thread_pool(); debug!("Creating keystore service"); - let service = KeystoreService::new_native_binder().unwrap(); + let dev = KeystoreService::new_native_binder().unwrap(); - debug!("Adding service to hub"); + let service = BnKeystoreService::new_binder(dev.clone()); + + debug!("Adding keystore service to hub"); + hub::add_service("keystore3", service.as_binder()).unwrap(); + + debug!("Adding OMK service to hub"); + let service = BnOhMyKsService::new_binder(dev); hub::add_service("omk", service.as_binder()).unwrap(); debug!("Joining thread pool"); rsbinder::ProcessState::join_thread_pool().unwrap(); } - -#[derive(Clone)] -pub struct KeystoreOperation; - -impl rsbinder::Interface for KeystoreOperation {} - -impl IKeystoreOperation::IKeystoreOperation for KeystoreOperation { - fn r#updateAad(&self, _arg_aad_input: &[u8]) -> rsbinder::status::Result<()> { - Ok(()) - } - - fn r#update(&self, _arg_input: &[u8]) -> rsbinder::status::Result>> { - Ok(Some(vec![])) - } - - fn r#finish( - &self, - _arg_input: Option<&[u8]>, - _arg_signature: Option<&[u8]>, - ) -> rsbinder::status::Result>> { - Ok(Some(vec![])) - } - - fn r#abort(&self) -> rsbinder::status::Result<()> { - Ok(()) - } -} diff --git a/src/plat/mod.rs b/src/plat/mod.rs index 86d4de3..8f5f3b9 100644 --- a/src/plat/mod.rs +++ b/src/plat/mod.rs @@ -1,2 +1,3 @@ pub mod property_watcher; pub mod utils; +pub mod pm; diff --git a/src/plat/pm.rs b/src/plat/pm.rs new file mode 100644 index 0000000..69c0ebf --- /dev/null +++ b/src/plat/pm.rs @@ -0,0 +1,231 @@ +pub mod IPackageManager { + #![allow(non_upper_case_globals, non_snake_case, dead_code)] + pub trait IPackageManager: rsbinder::Interface + Send { + fn descriptor() -> &'static str + where + Self: Sized, + { + "android.content.pm.IPackageManager" + } + fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result>; + fn getDefaultImpl() -> Option + where + Self: Sized, + { + DEFAULT_IMPL.get().cloned() + } + fn setDefaultImpl(d: IPackageManagerDefaultRef) -> IPackageManagerDefaultRef + where + Self: Sized, + { + DEFAULT_IMPL.get_or_init(|| d).clone() + } + } + pub trait IPackageManagerAsync

: rsbinder::Interface + Send { + fn descriptor() -> &'static str + where + Self: Sized, + { + "android.content.pm.IPackageManager" + } + fn r#getPackagesForUid<'a>( + &'a self, + _arg_uid: i32, + ) -> rsbinder::BoxFuture<'a, rsbinder::status::Result>>; + } + #[::async_trait::async_trait] + pub trait IPackageManagerAsyncService: rsbinder::Interface + Send { + fn descriptor() -> &'static str + where + Self: Sized, + { + "android.content.pm.IPackageManager" + } + async fn r#getPackagesForUid(&self, _arg_uid: i32) + -> rsbinder::status::Result>; + } + impl BnPackageManager { + pub fn new_async_binder(inner: T, rt: R) -> rsbinder::Strong + where + T: IPackageManagerAsyncService + Sync + Send + 'static, + R: rsbinder::BinderAsyncRuntime + Send + Sync + 'static, + { + struct Wrapper { + _inner: T, + _rt: R, + } + impl rsbinder::Interface for Wrapper + where + T: rsbinder::Interface, + R: Send + Sync, + { + fn as_binder(&self) -> rsbinder::SIBinder { + self._inner.as_binder() + } + fn dump( + &self, + _writer: &mut dyn std::io::Write, + _args: &[String], + ) -> rsbinder::Result<()> { + self._inner.dump(_writer, _args) + } + } + impl BnPackageManagerAdapter for Wrapper + where + T: IPackageManagerAsyncService + Sync + Send + 'static, + R: rsbinder::BinderAsyncRuntime + Send + Sync + 'static, + { + fn as_sync(&self) -> &dyn IPackageManager { + self + } + fn as_async(&self) -> &dyn IPackageManagerAsyncService { + &self._inner + } + } + impl IPackageManager for Wrapper + where + T: IPackageManagerAsyncService + Sync + Send + 'static, + R: rsbinder::BinderAsyncRuntime + Send + Sync + 'static, + { + fn r#getPackagesForUid( + &self, + _arg_uid: i32, + ) -> rsbinder::status::Result> { + self._rt.block_on(self._inner.r#getPackagesForUid(_arg_uid)) + } + } + let wrapped = Wrapper { + _inner: inner, + _rt: rt, + }; + let binder = rsbinder::native::Binder::new_with_stability( + BnPackageManager(Box::new(wrapped)), + rsbinder::Stability::default(), + ); + rsbinder::Strong::new(Box::new(binder)) + } + } + pub trait IPackageManagerDefault: Send + Sync { + fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result> { + Err(rsbinder::StatusCode::UnknownTransaction.into()) + } + } + pub(crate) mod transactions { + pub(crate) const r#getPackagesForUid: rsbinder::TransactionCode = + rsbinder::FIRST_CALL_TRANSACTION + 0; + } + pub type IPackageManagerDefaultRef = std::sync::Arc; + static DEFAULT_IMPL: std::sync::OnceLock = + std::sync::OnceLock::new(); + rsbinder::declare_binder_interface! { + IPackageManager["android.content.pm.IPackageManager"] { + native: { + BnPackageManager(on_transact), + adapter: BnPackageManagerAdapter, + r#async: IPackageManagerAsyncService, + }, + proxy: BpPackageManager, + r#async: IPackageManagerAsync, + } + } + impl BpPackageManager { + fn build_parcel_getPackagesForUid( + &self, + _arg_uid: i32, + ) -> rsbinder::Result { + let mut data = self.binder.as_proxy().unwrap().prepare_transact(true)?; + data.write(&_arg_uid)?; + Ok(data) + } + fn read_response_getPackagesForUid( + &self, + _arg_uid: i32, + _aidl_reply: rsbinder::Result>, + ) -> rsbinder::status::Result> { + if let Err(rsbinder::StatusCode::UnknownTransaction) = _aidl_reply { + if let Some(_aidl_default_impl) = ::getDefaultImpl() { + return _aidl_default_impl.r#getPackagesForUid(_arg_uid); + } + } + let mut _aidl_reply = _aidl_reply?.ok_or(rsbinder::StatusCode::UnexpectedNull)?; + let _status = _aidl_reply.read::()?; + if !_status.is_ok() { + return Err(_status); + } + let _aidl_return: Vec = _aidl_reply.read()?; + Ok(_aidl_return) + } + } + impl IPackageManager for BpPackageManager { + fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result> { + let _aidl_data = self.build_parcel_getPackagesForUid(_arg_uid)?; + let _aidl_reply = self.binder.as_proxy().unwrap().submit_transact( + transactions::r#getPackagesForUid, + &_aidl_data, + rsbinder::FLAG_CLEAR_BUF, + ); + self.read_response_getPackagesForUid(_arg_uid, _aidl_reply) + } + } + impl IPackageManagerAsync

for BpPackageManager { + fn r#getPackagesForUid<'a>( + &'a self, + _arg_uid: i32, + ) -> rsbinder::BoxFuture<'a, rsbinder::status::Result>> { + let _aidl_data = match self.build_parcel_getPackagesForUid(_arg_uid) { + Ok(_aidl_data) => _aidl_data, + Err(err) => return Box::pin(std::future::ready(Err(err.into()))), + }; + let binder = self.binder.clone(); + P::spawn( + move || { + binder.as_proxy().unwrap().submit_transact( + transactions::r#getPackagesForUid, + &_aidl_data, + rsbinder::FLAG_CLEAR_BUF | rsbinder::FLAG_PRIVATE_LOCAL, + ) + }, + move |_aidl_reply| async move { + self.read_response_getPackagesForUid(_arg_uid, _aidl_reply) + }, + ) + } + } + impl IPackageManagerAsync

for rsbinder::Binder { + fn r#getPackagesForUid<'a>( + &'a self, + _arg_uid: i32, + ) -> rsbinder::BoxFuture<'a, rsbinder::status::Result>> { + self.0.as_async().r#getPackagesForUid(_arg_uid) + } + } + impl IPackageManager for rsbinder::Binder { + fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result> { + self.0.as_sync().r#getPackagesForUid(_arg_uid) + } + } + fn on_transact( + _service: &dyn IPackageManager, + _code: rsbinder::TransactionCode, + _reader: &mut rsbinder::Parcel, + _reply: &mut rsbinder::Parcel, + ) -> rsbinder::Result<()> { + match _code { + transactions::r#getPackagesForUid => { + let _arg_uid: i32 = _reader.read()?; + let _aidl_return = _service.r#getPackagesForUid(_arg_uid); + match &_aidl_return { + Ok(_aidl_return) => { + _reply.write(&rsbinder::Status::from(rsbinder::StatusCode::Ok))?; + _reply.write(_aidl_return)?; + } + Err(_aidl_status) => { + _reply.write(_aidl_status)?; + } + } + Ok(()) + } + _ => Err(rsbinder::StatusCode::UnknownTransaction), + } + } +} diff --git a/src/plat/utils.rs b/src/plat/utils.rs index 82e4d82..2e5cefc 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use anyhow::Ok; use log::debug; @@ -7,39 +7,52 @@ use x509_cert::der::asn1::OctetString; use crate::android::content::pm::IPackageManager::IPackageManager; use crate::android::apex::IApexService::IApexService; +use crate::err; use crate::keymaster::apex::ApexModuleInfo; use std::cell::RefCell; thread_local! { - static PM: RefCell>> = RefCell::new(None); - static APEX: RefCell>> = RefCell::new(None); + static PM: Mutex>> = Mutex::new(None); + static APEX: Mutex>> = Mutex::new(None); } -struct MyDeathRecipient; +struct PmDeathRecipient; -impl rsbinder::DeathRecipient for MyDeathRecipient { +impl rsbinder::DeathRecipient for PmDeathRecipient { fn binder_died(&self, _who: &rsbinder::WIBinder) { PM.with(|p| { - *p.borrow_mut() = None; + *p.lock().unwrap() = None; }); debug!("PackageManager died, cleared PM instance"); } } +struct ApexDeathRecipient; + +impl rsbinder::DeathRecipient for ApexDeathRecipient { + fn binder_died(&self, _who: &rsbinder::WIBinder) { + APEX.with(|p| { + *p.lock().unwrap() = None; + }); + debug!("ApexService died, cleared APEX instance"); + } +} + #[allow(non_snake_case)] fn get_pm() -> anyhow::Result> { PM.with(|p| { - if let Some(iPm) = p.borrow().as_ref() { + let mut guard = p.lock().unwrap(); + if let Some(iPm) = guard.as_ref() { Ok(iPm.clone()) } else { let pm: rsbinder::Strong = hub::get_interface("package")?; - let recipient = Arc::new(MyDeathRecipient {}); + let recipient = Arc::new(PmDeathRecipient {}); pm.as_binder() .link_to_death(Arc::downgrade(&(recipient as Arc)))?; - *p.borrow_mut() = Some(pm.clone()); + *guard = Some(pm.clone()); Ok(pm) } }) @@ -48,17 +61,18 @@ fn get_pm() -> anyhow::Result> { #[allow(non_snake_case)] fn get_apex() -> anyhow::Result> { APEX.with(|p| { - if let Some(iPm) = p.borrow().as_ref() { - Ok(iPm.clone()) + let mut guard = p.lock().unwrap(); + if let Some(iApex) = guard.as_ref() { + Ok(iApex.clone()) } else { - let pm: rsbinder::Strong = hub::get_interface("apexservice")?; - let recipient = Arc::new(MyDeathRecipient {}); + let apex: rsbinder::Strong = hub::get_interface("apexservice")?; + let recipient = Arc::new(ApexDeathRecipient {}); - pm.as_binder() + apex.as_binder() .link_to_death(Arc::downgrade(&(recipient as Arc)))?; - *p.borrow_mut() = Some(pm.clone()); - Ok(pm) + *guard = Some(apex.clone()); + Ok(apex) } }) } @@ -68,7 +82,8 @@ pub fn get_aaid(uid: u32) -> anyhow::Result { return Ok("android".to_string()); } // system or root let pm = get_pm()?; - let package_names = pm.getPackagesForUid(uid as i32)?; + let package_names = pm.getPackagesForUid(uid as i32) + .map_err(|e| anyhow::anyhow!(err!("getPackagesForUid failed: {:?}", e)))?; debug!("get_aaid: package_name = {:?}", package_names); @@ -77,7 +92,8 @@ pub fn get_aaid(uid: u32) -> anyhow::Result { pub fn get_apex_module_info() -> anyhow::Result> { let apex = get_apex()?; - let result: Vec = apex.getAllPackages()?; + let result: Vec = apex.getAllPackages() + .map_err(|e| anyhow::anyhow!(err!("getAllPackages failed: {:?}", e)))?; let result: Vec = result .iter() @@ -87,7 +103,8 @@ pub fn get_apex_module_info() -> anyhow::Result> { version_code: i.versionCode as u64, }) }) - .collect::>>()?; + .collect::>>() + .map_err(|e| anyhow::anyhow!(err!("ApexModuleInfo conversion failed: {:?}", e)))?; Ok(result) } From 0d1cdb6f6f045d924383c2d2f6ab5303a534f89f Mon Sep 17 00:00:00 2001 From: qwq233 Date: Wed, 15 Oct 2025 01:47:59 +0800 Subject: [PATCH 15/46] fix: unable to retrieving aaid Signed-off-by: qwq233 --- aidl/android/apex/ApexInfo.aidl | 32 ++- aidl/android/apex/ApexInfoList.aidl | 23 ++ aidl/android/apex/ApexSessionInfo.aidl | 33 +++ aidl/android/apex/ApexSessionParams.aidl | 25 ++ aidl/android/apex/CompressedApexInfo.aidl | 23 ++ aidl/android/apex/CompressedApexInfoList.aidl | 23 ++ aidl/android/apex/IApexService.aidl | 93 ++++++- .../IKeyAttestationApplicationIdProvider.aidl | 34 +++ .../keystore/KeyAttestationApplicationId.aidl | 31 +++ .../keystore/KeyAttestationPackageInfo.aidl | 33 +++ aidl/android/security/keystore/Signature.aidl | 31 +++ build.rs | 5 +- src/keymaster/security_level.rs | 2 +- src/plat/aaid.rs | 13 + src/plat/mod.rs | 2 +- src/plat/pm.rs | 231 ------------------ src/plat/utils.rs | 102 ++++++-- 17 files changed, 479 insertions(+), 257 deletions(-) create mode 100644 aidl/android/apex/ApexInfoList.aidl create mode 100644 aidl/android/apex/ApexSessionInfo.aidl create mode 100644 aidl/android/apex/ApexSessionParams.aidl create mode 100644 aidl/android/apex/CompressedApexInfo.aidl create mode 100644 aidl/android/apex/CompressedApexInfoList.aidl create mode 100644 aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl create mode 100644 aidl/android/security/keystore/KeyAttestationApplicationId.aidl create mode 100644 aidl/android/security/keystore/KeyAttestationPackageInfo.aidl create mode 100644 aidl/android/security/keystore/Signature.aidl create mode 100644 src/plat/aaid.rs delete mode 100644 src/plat/pm.rs diff --git a/aidl/android/apex/ApexInfo.aidl b/aidl/android/apex/ApexInfo.aidl index e7dc306..aa2ae95 100644 --- a/aidl/android/apex/ApexInfo.aidl +++ b/aidl/android/apex/ApexInfo.aidl @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.apex; + parcelable ApexInfo { @utf8InCpp String moduleName; @utf8InCpp String modulePath; @@ -22,4 +24,32 @@ parcelable ApexInfo { @utf8InCpp String versionName; boolean isFactory; boolean isActive; -} \ No newline at end of file + + // Populated only for getStagedApex() API + boolean hasClassPathJars; + + // Will be set to true if during this boot a different APEX package of the APEX was + // activated, than in the previous boot. + // This can happen in the following situations: + // 1. It was part of the staged session that was applied during this boot. + // 2. A compressed system APEX was decompressed during this boot. + // 3. apexd failed to activate an APEX on /data/apex/active (that was successfully + // activated during last boot) and needed to fallback to pre-installed counterpart. + // Note: this field can only be set to true during boot, after boot is completed + // (sys.boot_completed = 1) value of this field will always be false. + boolean activeApexChanged; + + /** + * The partition that an APEX is pre-installed in or maps to. + */ + enum Partition { + SYSTEM, + SYSTEM_EXT, + PRODUCT, + VENDOR, + ODM + } + + // For pre-installed APEX, this is the partition where it is pre-installed. For brand-new APEX, this is the partition where its credential is pre-installed. + Partition partition; +} diff --git a/aidl/android/apex/ApexInfoList.aidl b/aidl/android/apex/ApexInfoList.aidl new file mode 100644 index 0000000..b048a85 --- /dev/null +++ b/aidl/android/apex/ApexInfoList.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.apex; + +import android.apex.ApexInfo; + +parcelable ApexInfoList { + ApexInfo[] apexInfos; +} diff --git a/aidl/android/apex/ApexSessionInfo.aidl b/aidl/android/apex/ApexSessionInfo.aidl new file mode 100644 index 0000000..65da113 --- /dev/null +++ b/aidl/android/apex/ApexSessionInfo.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 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. + */ + +package android.apex; + +parcelable ApexSessionInfo { + int sessionId; + // Maps to apex::proto::SessionState::State enum. + boolean isUnknown; + boolean isVerified; + boolean isStaged; + boolean isActivated; + boolean isRevertInProgress; + boolean isActivationFailed; + boolean isSuccess; + boolean isReverted; + boolean isRevertFailed; + @utf8InCpp String crashingNativeProcess; + @utf8InCpp String errorMessage; +} diff --git a/aidl/android/apex/ApexSessionParams.aidl b/aidl/android/apex/ApexSessionParams.aidl new file mode 100644 index 0000000..942f5aa --- /dev/null +++ b/aidl/android/apex/ApexSessionParams.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 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. + */ + +package android.apex; + +parcelable ApexSessionParams { + int sessionId = 0; + int[] childSessionIds = {}; + boolean hasRollbackEnabled = false; + boolean isRollback = false; + int rollbackId = 0; +} diff --git a/aidl/android/apex/CompressedApexInfo.aidl b/aidl/android/apex/CompressedApexInfo.aidl new file mode 100644 index 0000000..48015bd --- /dev/null +++ b/aidl/android/apex/CompressedApexInfo.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.apex; + +parcelable CompressedApexInfo { + @utf8InCpp String moduleName; + long versionCode; + long decompressedSize; +} diff --git a/aidl/android/apex/CompressedApexInfoList.aidl b/aidl/android/apex/CompressedApexInfoList.aidl new file mode 100644 index 0000000..e1620bd --- /dev/null +++ b/aidl/android/apex/CompressedApexInfoList.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.apex; + +import android.apex.CompressedApexInfo; + +parcelable CompressedApexInfoList { + CompressedApexInfo[] apexInfos; +} diff --git a/aidl/android/apex/IApexService.aidl b/aidl/android/apex/IApexService.aidl index 653c747..c65bf7a 100644 --- a/aidl/android/apex/IApexService.aidl +++ b/aidl/android/apex/IApexService.aidl @@ -13,11 +13,102 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.apex; import android.apex.ApexInfo; +import android.apex.ApexInfoList; +import android.apex.ApexSessionInfo; +import android.apex.ApexSessionParams; +import android.apex.CompressedApexInfoList; interface IApexService { + void submitStagedSession(in ApexSessionParams params, out ApexInfoList packages); + void markStagedSessionReady(int session_id); + void markStagedSessionSuccessful(int session_id); + + ApexSessionInfo[] getSessions(); + ApexSessionInfo getStagedSessionInfo(int session_id); + ApexInfo[] getStagedApexInfos(in ApexSessionParams params); ApexInfo[] getActivePackages(); ApexInfo[] getAllPackages(); -} \ No newline at end of file + + void abortStagedSession(int session_id); + void revertActiveSessions(); + + /** + * Copies the CE apex data directory for the given user to the backup + * location. + */ + void snapshotCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name); + + /** + * Restores the snapshot of the CE apex data directory for the given user and + * apex. Note the snapshot will be deleted after restoration succeeded. + */ + void restoreCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name); + + /** + * Deletes device-encrypted snapshots for the given rollback id. + */ + void destroyDeSnapshots(int rollback_id); + + /** + * Deletes credential-encrypted snapshots for the given user, for the given rollback id. + */ + void destroyCeSnapshots(int user_id, int rollback_id); + + /** + * Deletes all credential-encrypted snapshots for the given user, except for + * those listed in retain_rollback_ids. + */ + void destroyCeSnapshotsNotSpecified(int user_id, in int[] retain_rollback_ids); + + void unstagePackages(in @utf8InCpp List active_package_paths); + + /** + * Returns the active package corresponding to |package_name| and null + * if none exists. + */ + ApexInfo getActivePackage(in @utf8InCpp String package_name); + + /** + * Not meant for use outside of testing. The call will not be + * functional on user builds. + */ + void stagePackages(in @utf8InCpp List package_tmp_paths); + /** + * Not meant for use outside of testing. The call will not be + * functional on user builds. + */ + void resumeRevertIfNeeded(); + /** + * Forces apexd to recollect pre-installed data from all the supported built-in dirs. + * + * Not meant for use outside of testing. This call will not be functional + * on user builds. Only root is allowed to call this method. + */ + void recollectPreinstalledData(); + + /** + * Informs apexd that the boot has completed. + */ + void markBootCompleted(); + + /** + * Assuming the provided compressed APEX will be installed on next boot, + * calculate how much space will be required for decompression + */ + long calculateSizeForCompressedApex(in CompressedApexInfoList compressed_apex_info_list); + + /** + * Reserve space on /data partition for compressed APEX decompression. Returns error if + * reservation fails. If empty list is passed, then reserved space is deallocated. + */ + void reserveSpaceForCompressedApex(in CompressedApexInfoList compressed_apex_info_list); + + /** + * Performs a non-staged install of the given APEX. + */ + ApexInfo installAndActivatePackage(in @utf8InCpp String packagePath, boolean force); +} diff --git a/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl b/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl new file mode 100644 index 0000000..cfc5980 --- /dev/null +++ b/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.security.keystore.KeyAttestationApplicationId; + +/** @hide */ +interface IKeyAttestationApplicationIdProvider { + const int ERROR_GET_ATTESTATION_APPLICATION_ID_FAILED = 1; + + /** + * Provides information describing the possible applications identified by a UID. + * + * In case of not getting package ids from uid return + * {@link #ERROR_GET_ATTESTATION_APPLICATION_ID_FAILED} to the caller. + * + * @hide + */ + KeyAttestationApplicationId getKeyAttestationApplicationId(int uid); +} diff --git a/aidl/android/security/keystore/KeyAttestationApplicationId.aidl b/aidl/android/security/keystore/KeyAttestationApplicationId.aidl new file mode 100644 index 0000000..c33e830 --- /dev/null +++ b/aidl/android/security/keystore/KeyAttestationApplicationId.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.security.keystore.KeyAttestationPackageInfo; + +/** + * @hide + * The information aggregated by this parcelable is used by keystore to identify a caller of the + * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore + * can only determine a caller by uid granularity, and a uid can be shared by multiple packages. + * The remote party must decide if it trusts all of the packages enough to consider the + * confidentiality of the key material in question intact. + */ +parcelable KeyAttestationApplicationId { + KeyAttestationPackageInfo[] packageInfos; +} diff --git a/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl b/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl new file mode 100644 index 0000000..5f647d0 --- /dev/null +++ b/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.security.keystore.Signature; + +/** + * @hide + * This parcelable constitutes and excerpt from the PackageManager's PackageInfo for the purpose of + * key attestation. It is part of the KeyAttestationApplicationId, which is used by + * keystore to identify the caller of the keystore API towards a remote party. + */ +parcelable KeyAttestationPackageInfo { + String packageName; + + long versionCode; + + Signature[] signatures; +} diff --git a/aidl/android/security/keystore/Signature.aidl b/aidl/android/security/keystore/Signature.aidl new file mode 100644 index 0000000..800499a --- /dev/null +++ b/aidl/android/security/keystore/Signature.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +/** + * @hide + * Represents a signature data read from the package file. Extracted from from the PackageManager's + * PackageInfo for the purpose of key attestation. It is part of the KeyAttestationPackageInfo, + * which is used by keystore to identify the caller of the keystore API towards a remote party. + */ +parcelable Signature { + /** + * Represents signing certificate data associated with application package, signatures are + * expected to be a hex-encoded ASCII string representing valid X509 certificate. + */ + byte[] data; +} diff --git a/build.rs b/build.rs index b9b6987..aa88b43 100644 --- a/build.rs +++ b/build.rs @@ -32,6 +32,7 @@ fn main() { "aidl/android/system/keystore2", "aidl/android/hardware/security/keymint", "aidl/android/security/metrics", + "aidl/android/security/keystore", "aidl/android/apex", "aidl/top/qwq2333/ohmykeymint", ]; @@ -47,9 +48,7 @@ fn main() { } } } - aidl.source(PathBuf::from( - "aidl/android/content/pm/IPackageManager.aidl", - )) + aidl .source(PathBuf::from( "aidl/android/security/authorization/ResponseCode.aidl", )) diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 00f6667..694e0c0 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -163,7 +163,7 @@ impl KeystoreSecurityLevel { Ok(aaid_ok) => { result.push(KeyParameter { tag: Tag::ATTESTATION_APPLICATION_ID, - value: KeyParameterValue::Blob(aaid_ok.into_bytes()), + value: KeyParameterValue::Blob(aaid_ok), }); } Err(e) => { diff --git a/src/plat/aaid.rs b/src/plat/aaid.rs new file mode 100644 index 0000000..91fd274 --- /dev/null +++ b/src/plat/aaid.rs @@ -0,0 +1,13 @@ +use der::asn1::{OctetString, SetOfVec}; + +#[derive(der::Sequence, Debug)] +pub struct AttestationApplicationId { + pub package_info_records: SetOfVec, + pub signature_digests: SetOfVec, +} + +#[derive(der::Sequence, der::ValueOrd, Debug)] +pub struct PackageInfoRecord { + pub package_name: OctetString, + pub version: i64 +} \ No newline at end of file diff --git a/src/plat/mod.rs b/src/plat/mod.rs index 8f5f3b9..dbf9fe3 100644 --- a/src/plat/mod.rs +++ b/src/plat/mod.rs @@ -1,3 +1,3 @@ pub mod property_watcher; pub mod utils; -pub mod pm; +pub mod aaid; diff --git a/src/plat/pm.rs b/src/plat/pm.rs deleted file mode 100644 index 69c0ebf..0000000 --- a/src/plat/pm.rs +++ /dev/null @@ -1,231 +0,0 @@ -pub mod IPackageManager { - #![allow(non_upper_case_globals, non_snake_case, dead_code)] - pub trait IPackageManager: rsbinder::Interface + Send { - fn descriptor() -> &'static str - where - Self: Sized, - { - "android.content.pm.IPackageManager" - } - fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result>; - fn getDefaultImpl() -> Option - where - Self: Sized, - { - DEFAULT_IMPL.get().cloned() - } - fn setDefaultImpl(d: IPackageManagerDefaultRef) -> IPackageManagerDefaultRef - where - Self: Sized, - { - DEFAULT_IMPL.get_or_init(|| d).clone() - } - } - pub trait IPackageManagerAsync

: rsbinder::Interface + Send { - fn descriptor() -> &'static str - where - Self: Sized, - { - "android.content.pm.IPackageManager" - } - fn r#getPackagesForUid<'a>( - &'a self, - _arg_uid: i32, - ) -> rsbinder::BoxFuture<'a, rsbinder::status::Result>>; - } - #[::async_trait::async_trait] - pub trait IPackageManagerAsyncService: rsbinder::Interface + Send { - fn descriptor() -> &'static str - where - Self: Sized, - { - "android.content.pm.IPackageManager" - } - async fn r#getPackagesForUid(&self, _arg_uid: i32) - -> rsbinder::status::Result>; - } - impl BnPackageManager { - pub fn new_async_binder(inner: T, rt: R) -> rsbinder::Strong - where - T: IPackageManagerAsyncService + Sync + Send + 'static, - R: rsbinder::BinderAsyncRuntime + Send + Sync + 'static, - { - struct Wrapper { - _inner: T, - _rt: R, - } - impl rsbinder::Interface for Wrapper - where - T: rsbinder::Interface, - R: Send + Sync, - { - fn as_binder(&self) -> rsbinder::SIBinder { - self._inner.as_binder() - } - fn dump( - &self, - _writer: &mut dyn std::io::Write, - _args: &[String], - ) -> rsbinder::Result<()> { - self._inner.dump(_writer, _args) - } - } - impl BnPackageManagerAdapter for Wrapper - where - T: IPackageManagerAsyncService + Sync + Send + 'static, - R: rsbinder::BinderAsyncRuntime + Send + Sync + 'static, - { - fn as_sync(&self) -> &dyn IPackageManager { - self - } - fn as_async(&self) -> &dyn IPackageManagerAsyncService { - &self._inner - } - } - impl IPackageManager for Wrapper - where - T: IPackageManagerAsyncService + Sync + Send + 'static, - R: rsbinder::BinderAsyncRuntime + Send + Sync + 'static, - { - fn r#getPackagesForUid( - &self, - _arg_uid: i32, - ) -> rsbinder::status::Result> { - self._rt.block_on(self._inner.r#getPackagesForUid(_arg_uid)) - } - } - let wrapped = Wrapper { - _inner: inner, - _rt: rt, - }; - let binder = rsbinder::native::Binder::new_with_stability( - BnPackageManager(Box::new(wrapped)), - rsbinder::Stability::default(), - ); - rsbinder::Strong::new(Box::new(binder)) - } - } - pub trait IPackageManagerDefault: Send + Sync { - fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result> { - Err(rsbinder::StatusCode::UnknownTransaction.into()) - } - } - pub(crate) mod transactions { - pub(crate) const r#getPackagesForUid: rsbinder::TransactionCode = - rsbinder::FIRST_CALL_TRANSACTION + 0; - } - pub type IPackageManagerDefaultRef = std::sync::Arc; - static DEFAULT_IMPL: std::sync::OnceLock = - std::sync::OnceLock::new(); - rsbinder::declare_binder_interface! { - IPackageManager["android.content.pm.IPackageManager"] { - native: { - BnPackageManager(on_transact), - adapter: BnPackageManagerAdapter, - r#async: IPackageManagerAsyncService, - }, - proxy: BpPackageManager, - r#async: IPackageManagerAsync, - } - } - impl BpPackageManager { - fn build_parcel_getPackagesForUid( - &self, - _arg_uid: i32, - ) -> rsbinder::Result { - let mut data = self.binder.as_proxy().unwrap().prepare_transact(true)?; - data.write(&_arg_uid)?; - Ok(data) - } - fn read_response_getPackagesForUid( - &self, - _arg_uid: i32, - _aidl_reply: rsbinder::Result>, - ) -> rsbinder::status::Result> { - if let Err(rsbinder::StatusCode::UnknownTransaction) = _aidl_reply { - if let Some(_aidl_default_impl) = ::getDefaultImpl() { - return _aidl_default_impl.r#getPackagesForUid(_arg_uid); - } - } - let mut _aidl_reply = _aidl_reply?.ok_or(rsbinder::StatusCode::UnexpectedNull)?; - let _status = _aidl_reply.read::()?; - if !_status.is_ok() { - return Err(_status); - } - let _aidl_return: Vec = _aidl_reply.read()?; - Ok(_aidl_return) - } - } - impl IPackageManager for BpPackageManager { - fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result> { - let _aidl_data = self.build_parcel_getPackagesForUid(_arg_uid)?; - let _aidl_reply = self.binder.as_proxy().unwrap().submit_transact( - transactions::r#getPackagesForUid, - &_aidl_data, - rsbinder::FLAG_CLEAR_BUF, - ); - self.read_response_getPackagesForUid(_arg_uid, _aidl_reply) - } - } - impl IPackageManagerAsync

for BpPackageManager { - fn r#getPackagesForUid<'a>( - &'a self, - _arg_uid: i32, - ) -> rsbinder::BoxFuture<'a, rsbinder::status::Result>> { - let _aidl_data = match self.build_parcel_getPackagesForUid(_arg_uid) { - Ok(_aidl_data) => _aidl_data, - Err(err) => return Box::pin(std::future::ready(Err(err.into()))), - }; - let binder = self.binder.clone(); - P::spawn( - move || { - binder.as_proxy().unwrap().submit_transact( - transactions::r#getPackagesForUid, - &_aidl_data, - rsbinder::FLAG_CLEAR_BUF | rsbinder::FLAG_PRIVATE_LOCAL, - ) - }, - move |_aidl_reply| async move { - self.read_response_getPackagesForUid(_arg_uid, _aidl_reply) - }, - ) - } - } - impl IPackageManagerAsync

for rsbinder::Binder { - fn r#getPackagesForUid<'a>( - &'a self, - _arg_uid: i32, - ) -> rsbinder::BoxFuture<'a, rsbinder::status::Result>> { - self.0.as_async().r#getPackagesForUid(_arg_uid) - } - } - impl IPackageManager for rsbinder::Binder { - fn r#getPackagesForUid(&self, _arg_uid: i32) -> rsbinder::status::Result> { - self.0.as_sync().r#getPackagesForUid(_arg_uid) - } - } - fn on_transact( - _service: &dyn IPackageManager, - _code: rsbinder::TransactionCode, - _reader: &mut rsbinder::Parcel, - _reply: &mut rsbinder::Parcel, - ) -> rsbinder::Result<()> { - match _code { - transactions::r#getPackagesForUid => { - let _arg_uid: i32 = _reader.read()?; - let _aidl_return = _service.r#getPackagesForUid(_arg_uid); - match &_aidl_return { - Ok(_aidl_return) => { - _reply.write(&rsbinder::Status::from(rsbinder::StatusCode::Ok))?; - _reply.write(_aidl_return)?; - } - Err(_aidl_status) => { - _reply.write(_aidl_status)?; - } - } - Ok(()) - } - _ => Err(rsbinder::StatusCode::UnknownTransaction), - } - } -} diff --git a/src/plat/utils.rs b/src/plat/utils.rs index 2e5cefc..0313d7c 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -1,19 +1,23 @@ use std::sync::{Arc, Mutex}; use anyhow::Ok; +use der::Encode; +use der::asn1::SetOfVec; +use kmr_common::crypto::Sha256; +use kmr_crypto_boring::sha256::BoringSha256; use log::debug; -use rsbinder::{hub, DeathRecipient}; -use x509_cert::der::asn1::OctetString; +use rsbinder::{hub, DeathRecipient, Parcel, Parcelable}; +use serde::de; -use crate::android::content::pm::IPackageManager::IPackageManager; use crate::android::apex::IApexService::IApexService; +use crate::android::security::keystore::IKeyAttestationApplicationIdProvider::IKeyAttestationApplicationIdProvider; +use crate::android::security::keystore::KeyAttestationApplicationId::KeyAttestationApplicationId; +use crate::android::security::keystore::KeyAttestationPackageInfo::KeyAttestationPackageInfo; use crate::err; use crate::keymaster::apex::ApexModuleInfo; -use std::cell::RefCell; - thread_local! { - static PM: Mutex>> = Mutex::new(None); + static PM: Mutex>> = Mutex::new(None); static APEX: Mutex>> = Mutex::new(None); } @@ -40,13 +44,16 @@ impl rsbinder::DeathRecipient for ApexDeathRecipient { } #[allow(non_snake_case)] -fn get_pm() -> anyhow::Result> { +fn get_pm() -> anyhow::Result> { PM.with(|p| { let mut guard = p.lock().unwrap(); if let Some(iPm) = guard.as_ref() { Ok(iPm.clone()) } else { - let pm: rsbinder::Strong = hub::get_interface("package")?; + let pm: rsbinder::Strong = + hub::get_interface( + "sec_key_att_app_id_provider", + )?; let recipient = Arc::new(PmDeathRecipient {}); pm.as_binder() @@ -77,29 +84,86 @@ fn get_apex() -> anyhow::Result> { }) } -pub fn get_aaid(uid: u32) -> anyhow::Result { - if (uid == 0) || (uid == 1000) { - return Ok("android".to_string()); - } // system or root - let pm = get_pm()?; - let package_names = pm.getPackagesForUid(uid as i32) - .map_err(|e| anyhow::anyhow!(err!("getPackagesForUid failed: {:?}", e)))?; +pub fn get_aaid(uid: u32) -> anyhow::Result> { + let application_id = if (uid == 0) || (uid == 1000) { + let mut info = KeyAttestationPackageInfo::default(); + info.packageName = "AndroidSystem".to_string(); + info.versionCode = 1; + KeyAttestationApplicationId { + packageInfos: vec![info], + } + } else { + let _wd = crate::watchdog::watch("get_aaid: Retrieving AAID by calling service"); + let pm = get_pm()?; + { + let current_uid = unsafe { libc::getuid() }; + let current_euid = unsafe { libc::geteuid() }; + + debug!("Current UID: {}, EUID: {}", current_uid, current_euid); + + unsafe { + libc::seteuid(1017); // KEYSTORE_UID + } + + let result = pm.getKeyAttestationApplicationId(uid as i32) + .map_err(|e| anyhow::anyhow!(err!("getPackagesForUid failed: {:?}", e)))?; + + unsafe { + libc::seteuid(current_euid); + } + + result + } + + }; + + debug!("Application ID: {:?}", application_id); + + encode_application_id(application_id) +} + +fn encode_application_id(application_id: KeyAttestationApplicationId) -> Result, anyhow::Error> { + let mut package_info_set = SetOfVec::new(); + let mut signature_digests = SetOfVec::new(); + let sha256 = BoringSha256 {}; + + for pkg in application_id.packageInfos { + for sig in pkg.signatures { + let result = sha256.hash(sig.data.as_slice()) + .map_err(|e| anyhow::anyhow!("Failed to hash signature: {:?}", e))?; + + let octet_string = x509_cert::der::asn1::OctetString::new(&result)?; + + signature_digests.insert_ordered(octet_string).map_err(|e| anyhow::anyhow!("Failed to encode AttestationApplicationId: {:?}", e))?; + } + + let package_info = super::aaid::PackageInfoRecord { + package_name: der::asn1::OctetString::new(pkg.packageName.as_bytes())?, + version: pkg.versionCode, + }; + package_info_set.insert_ordered(package_info).map_err(|e| anyhow::anyhow!("Failed to encode AttestationApplicationId: {:?}", e))?; + } - debug!("get_aaid: package_name = {:?}", package_names); + let result = super::aaid::AttestationApplicationId { + package_info_records: package_info_set, + signature_digests: signature_digests, + }; - Ok(package_names[0].clone()) + result.to_der() + .map_err(|e| anyhow::anyhow!("Failed to encode AttestationApplicationId: {:?}", e)) } pub fn get_apex_module_info() -> anyhow::Result> { let apex = get_apex()?; - let result: Vec = apex.getAllPackages() + let result: Vec = apex + .getAllPackages() .map_err(|e| anyhow::anyhow!(err!("getAllPackages failed: {:?}", e)))?; let result: Vec = result .iter() .map(|i| { Ok(ApexModuleInfo { - package_name: OctetString::new(i.moduleName.as_bytes())?, + package_name: der::asn1::OctetString::new(i.moduleName.as_bytes())?, version_code: i.versionCode as u64, }) }) From 400e0a9b8d18d9723f9c4046b9345bfea07cc771 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 17 Oct 2025 15:08:57 +0800 Subject: [PATCH 16/46] chore: add editorconfig Signed-off-by: qwq233 --- .editorconfig | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dc0fc7f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 From b2803982291eb3f723cdd4128176aad890320f87 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 18 Oct 2025 03:03:51 +0800 Subject: [PATCH 17/46] feat: allow reset current using keybox Signed-off-by: qwq233 --- .gitignore | 4 +- Cargo.toml | 3 + .../qwq2333/ohmykeymint/IOhMyKsService.aidl | 6 +- build.rs | 3 +- common/Cargo.toml | 1 + common/src/crypto/traits.rs | 1 + common/src/keyblob.rs | 5 + src/att_mgr.rs | 32 ++ src/config.rs | 339 ++++++++++++++++++ src/global.rs | 32 +- src/{keymint/attest.rs => keybox.rs} | 131 +++++-- src/keymaster/apex.rs | 11 +- src/keymaster/async_task.rs | 12 +- src/keymaster/database/utils.rs | 10 +- src/keymaster/db.rs | 87 ++++- src/keymaster/gc.rs | 23 +- src/keymaster/keymint_device.rs | 253 +++++++------ src/keymaster/mod.rs | 4 +- src/keymaster/security_level.rs | 72 ++-- src/keymaster/service.rs | 233 +++++++++--- src/keymaster/utils.rs | 13 +- src/keymint/mod.rs | 1 - src/keymint/sdd.rs | 4 +- src/keymint/soft.rs | 21 +- src/logging.rs | 20 +- src/main.rs | 42 ++- src/plat/aaid.rs | 4 +- src/plat/mod.rs | 2 +- src/plat/utils.rs | 96 +++-- ta/src/keys.rs | 6 + 30 files changed, 1134 insertions(+), 337 deletions(-) create mode 100644 src/att_mgr.rs create mode 100644 src/config.rs rename src/{keymint/attest.rs => keybox.rs} (84%) diff --git a/.gitignore b/.gitignore index 32e25f3..309b8dd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ Cargo.lock hex.txt parcel.bin -src/aidl.rs \ No newline at end of file +src/aidl.rs +cert.crt +cert.der diff --git a/Cargo.toml b/Cargo.toml index d2f9a42..e73fa78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,9 @@ rsproperties = { version = "0.2.2", features = ["builder"] } libsqlite3-sys = "0.35.0" rand = "0.9.2" der = "0.7.10" +toml = "0.9.8" +regex = "1.12.2" +hotwatch = "0.5.0" [target.'cfg(target_os = "linux")'.dependencies] rsproperties-service = "*" diff --git a/aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl b/aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl index 433bb88..5ccd7ed 100644 --- a/aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl +++ b/aidl/top/qwq2333/ohmykeymint/IOhMyKsService.aidl @@ -1,6 +1,7 @@ package top.qwq2333.ohmykeymint; import android.hardware.security.keymint.SecurityLevel; +import android.hardware.security.keymint.Certificate; import android.hardware.security.keymint.Tag; import android.system.keystore2.Domain; import android.system.keystore2.IKeystoreSecurityLevel; @@ -41,6 +42,9 @@ interface IOhMyKsService { KeyDescriptor[] listEntriesBatched(in @nullable CallerInfo ctx, in Domain domain, in long nspace, in @nullable String startingPastAlias); - byte[] getSupplementaryAttestationInfo(in Tag tag); + + void updateEcKeybox(in byte[] key, in List chain); + + void updateRsaKeybox(in byte[] key, in List chain); } \ No newline at end of file diff --git a/build.rs b/build.rs index aa88b43..8f5448a 100644 --- a/build.rs +++ b/build.rs @@ -48,8 +48,7 @@ fn main() { } } } - aidl - .source(PathBuf::from( + aidl.source(PathBuf::from( "aidl/android/security/authorization/ResponseCode.aidl", )) .source(PathBuf::from( diff --git a/common/Cargo.toml b/common/Cargo.toml index 6e086c8..822b523 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -20,6 +20,7 @@ kmr-wire = "*" log = "^0.4" pkcs1 = { version = "^0.7.5", features = ["alloc"] } pkcs8 = "^0.10.2" +rand = "0.9.2" sec1 = { version = "0.7.3", features = ["alloc", "der", "pkcs8"] } spki = { version = "0.7.3"} zeroize = { version = "^1.5.6", features = ["alloc", "zeroize_derive"] } diff --git a/common/src/crypto/traits.rs b/common/src/crypto/traits.rs index c5c8f8f..b88789b 100644 --- a/common/src/crypto/traits.rs +++ b/common/src/crypto/traits.rs @@ -18,6 +18,7 @@ use crate::{crypto::ec::Key, der_err, explicit, keyblob, vec_try, Error}; use der::Decode; use kmr_wire::{keymint, keymint::Digest, KeySizeInBits, RsaExponent}; use log::{error, warn}; +use rand::RngCore as _; /// Combined collection of trait implementations that must be provided. pub struct Implementation { diff --git a/common/src/keyblob.rs b/common/src/keyblob.rs index 3b77642..600b036 100644 --- a/common/src/keyblob.rs +++ b/common/src/keyblob.rs @@ -366,6 +366,11 @@ pub fn encrypt( ) -> Result { // Determine if secure deletion is required by examining the key characteristics at our // security level. + log::debug!( + "Encrypting keyblob with characteristics: {:?}, SecLevel={:?}", + plaintext_keyblob.characteristics, + sec_level + ); let requires_sdd = plaintext_keyblob .characteristics_at(sec_level)? .iter() diff --git a/src/att_mgr.rs b/src/att_mgr.rs new file mode 100644 index 0000000..757dd74 --- /dev/null +++ b/src/att_mgr.rs @@ -0,0 +1,32 @@ +use kmr_common::km_err; +use kmr_ta::device::RetrieveAttestationIds; +use kmr_wire::AttestationIdInfo; + +use crate::config::CONFIG; + +pub struct AttestationIdMgr; + +impl RetrieveAttestationIds for AttestationIdMgr { + fn get(&self) -> Result { + let config = CONFIG + .read() + .map_err(|_| km_err!(UnknownError, "config lock poisoned"))?; + + Ok(AttestationIdInfo { + brand: config.device.brand.clone().into_bytes(), + device: config.device.device.clone().into_bytes(), + product: config.device.product.clone().into_bytes(), + serial: config.device.serial.clone().into_bytes(), + imei: config.device.imei.clone().into_bytes(), + imei2: config.device.imei2.clone().into_bytes(), + meid: config.device.meid.clone().into_bytes(), + manufacturer: config.device.manufacturer.clone().into_bytes(), + model: config.device.model.clone().into_bytes(), + }) + } + + fn destroy_all(&mut self) -> Result<(), kmr_common::Error> { + // ignore this + Ok(()) + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..20801f0 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,339 @@ +use core::panic; +use std::sync::RwLock; + +use hotwatch::Hotwatch; +use kmr_common::crypto::Rng; +use kmr_crypto_boring::rng::BoringRng; +use serde::{ser::SerializeStruct, Deserialize, Serialize}; + +lazy_static::lazy_static! { + pub static ref CONFIG: RwLock = init_config(); +} + +#[cfg(target_os = "android")] +const CONFIG_PATH: &str = "/data/adb/omk/config.toml"; + +#[cfg(not(target_os = "android"))] +const CONFIG_PATH: &str = "./omk/config.toml"; + +fn init_config() -> RwLock { + let config = std::fs::read_to_string(CONFIG_PATH); + let config: Config = match config { + Ok(s) => match toml::from_str(&s) { + Ok(c) => c, + Err(e) => { + log::error!("Failed to parse config file, using default: {:?}", e); + Config::default() + } + }, + Err(e) => { + log::error!("Failed to read config file, using default: {:?}", e); + Config::default() + } + }; + + // write back the config file to ensure it's always present + let s = toml::to_string_pretty(&config).unwrap(); + if let Err(e) = std::fs::create_dir_all(std::path::Path::new(CONFIG_PATH).parent().unwrap()) { + log::error!("Failed to create config directory: {:?}", e); + } else if let Err(e) = { + // backup old config + if std::path::Path::new(CONFIG_PATH).exists() { + let backup_path = format!("{}.bak", CONFIG_PATH); + if let Err(e) = std::fs::copy(CONFIG_PATH, &backup_path) { + log::error!("Failed to backup config file: {:?}", e); + } else { + log::info!("Backed up old config file to {}", backup_path); + } + } + std::fs::write(CONFIG_PATH, s) + } { + log::error!("Failed to write config file: {:?}", e); + panic!("Failed to write config file: {:?}", e); + } + + std::thread::spawn(|| { + let mut watcher = Hotwatch::new().unwrap(); + watcher + .watch(CONFIG_PATH, |event| { + log::info!("Config file changed: {:?}", event); + let config = std::fs::read_to_string(CONFIG_PATH); + let config: Config = match config { + Ok(s) => match toml::from_str(&s) { + Ok(c) => c, + Err(e) => { + log::error!("Failed to parse config file, ignoring change: {:?}", e); + return; + } + }, + Err(e) => { + log::error!("Failed to read config file, ignoring change: {:?}", e); + return; + } + }; + let mut cfg = CONFIG.write().unwrap(); + *cfg = config; + log::info!("Config updated"); + }) + .unwrap(); + loop { + std::thread::park(); + } + }); + + RwLock::new(config) +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Config { + pub main: MainConfig, + pub crypto: CryptoConfig, + pub trust: TrustConfig, + pub device: DeviceProperty, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Backend { + TrickyStore, + OMK, +} + +impl std::fmt::Display for Backend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Backend::TrickyStore => write!(f, "ts"), + Backend::OMK => write!(f, "omk"), + } + } +} + +impl std::str::FromStr for Backend { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "ts" => Ok(Backend::TrickyStore), + "omk" => Ok(Backend::OMK), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MainConfig { + /// default to tricky store for compatibility, we will + /// switch to omk later when we are sure everything works + pub backend: Backend, +} + +impl Default for MainConfig { + fn default() -> Self { + Self { + backend: Backend::TrickyStore, + } + } +} + +#[derive(Debug, Clone)] +pub struct CryptoConfig { + pub root_kek_seed: [u8; 32], + pub kak_seed: [u8; 32], +} + +impl Serialize for CryptoConfig { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CryptoConfig", 2)?; + state.serialize_field("root_kek_seed", &hex::encode(self.root_kek_seed))?; + state.serialize_field("kak_seed", &hex::encode(self.kak_seed))?; + state.end() + } +} + +impl<'de> Deserialize<'de> for CryptoConfig { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct CryptoConfigHelper { + root_kek_seed: String, + kak_seed: String, + } + + let helper = CryptoConfigHelper::deserialize(deserializer)?; + let root_kek_seed = hex::decode(&helper.root_kek_seed).map_err(serde::de::Error::custom)?; + let kak_seed = hex::decode(&helper.kak_seed).map_err(serde::de::Error::custom)?; + + if root_kek_seed.len() != 32 { + return Err(serde::de::Error::custom("root_kek_seed must be 32 bytes")); + } + if kak_seed.len() != 32 { + return Err(serde::de::Error::custom("kak_seed must be 32 bytes")); + } + + let mut root_kek_array = [0u8; 32]; + root_kek_array.copy_from_slice(&root_kek_seed); + + let mut kak_array = [0u8; 32]; + kak_array.copy_from_slice(&kak_seed); + + Ok(CryptoConfig { + root_kek_seed: root_kek_array, + kak_seed: kak_array, + }) + } +} + +impl Default for CryptoConfig { + fn default() -> Self { + let mut rng = BoringRng {}; + Self { + root_kek_seed: { + let mut key = [0u8; 32]; + rng.fill_bytes(&mut key); + key + }, + kak_seed: { + let mut key = [0u8; 32]; + rng.fill_bytes(&mut key); + key + }, + } + } +} + +#[derive(Debug, Clone)] +pub struct TrustConfig { + pub os_version: i32, + pub security_patch: String, + pub vb_key: [u8; 32], // hex encoded + pub vb_hash: [u8; 32], // hex encoded + + pub verified_boot_state: bool, // you sure? + pub device_locked: bool, +} + +impl Serialize for TrustConfig { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("TrustConfig", 6)?; + state.serialize_field("os_version", &self.os_version)?; + state.serialize_field("security_patch", &self.security_patch)?; + state.serialize_field("vb_key", &hex::encode(self.vb_key))?; + state.serialize_field("vb_hash", &hex::encode(self.vb_hash))?; + state.serialize_field("verified_boot_state", &self.verified_boot_state)?; + state.serialize_field("device_locked", &self.device_locked)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for TrustConfig { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct TrustConfigHelper { + os_version: i32, + security_patch: String, + vb_key: String, + vb_hash: String, + verified_boot_state: bool, + device_locked: bool, + } + + let helper = TrustConfigHelper::deserialize(deserializer)?; + let vb_key = hex::decode(&helper.vb_key).map_err(serde::de::Error::custom)?; + let vb_hash = hex::decode(&helper.vb_hash).map_err(serde::de::Error::custom)?; + + if vb_key.len() != 32 { + return Err(serde::de::Error::custom("vb_key must be 32 bytes")); + } + if vb_hash.len() != 32 { + return Err(serde::de::Error::custom("vb_hash must be 32 bytes")); + } + + let mut vb_key_array = [0u8; 32]; + vb_key_array.copy_from_slice(&vb_key); + + let mut vb_hash_array = [0u8; 32]; + vb_hash_array.copy_from_slice(&vb_hash); + + // check security patch format + if !regex::Regex::new(r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$") + .unwrap() + .is_match(&helper.security_patch) + { + return Err(serde::de::Error::custom( + "security_patch must be in YYYY-MM-DD format", + )); + } + + Ok(TrustConfig { + os_version: helper.os_version, + security_patch: helper.security_patch, + vb_key: vb_key_array, + vb_hash: vb_hash_array, + verified_boot_state: helper.verified_boot_state, + device_locked: helper.device_locked, + }) + } +} + +impl Default for TrustConfig { + fn default() -> Self { + let mut rng = BoringRng {}; + + let mut vb_key = [0u8; 32]; + rng.fill_bytes(&mut vb_key); + let mut vb_hash = [0u8; 32]; + rng.fill_bytes(&mut vb_hash); + + Self { + os_version: rsproperties::get_or("ro.build.version.release", 35 /* Android 15 */), + security_patch: rsproperties::get_or( + "ro.build.version.security_patch", + "2024-01-05".to_string(), + ), + vb_key, + vb_hash, + verified_boot_state: true, // rsproperties::get_or("ro.boot.verifiedbootstate", "green".to_string()) != "orange", + device_locked: true, // rsproperties::get_or("ro.boot.verifiedbootstate", "green".to_string()) != "orange", + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeviceProperty { + pub brand: String, + pub device: String, + pub product: String, + pub manufacturer: String, + pub model: String, + pub serial: String, + + pub meid: String, + pub imei: String, + pub imei2: String, +} + +impl Default for DeviceProperty { + fn default() -> Self { + Self { + brand: rsproperties::get_or("ro.product.brand", "google".to_string()), + device: rsproperties::get_or("ro.product.device", "generic".to_string()), + product: rsproperties::get_or("ro.product.name", "mainline".to_string()), + manufacturer: rsproperties::get_or("ro.product.manufacturer", "google".to_string()), + model: rsproperties::get_or("ro.product.model", "mainline".to_string()), + serial: rsproperties::get_or("ro.serialno", "f7bade12".to_string()), + meid: rsproperties::get_or("ro.ril.oem.meid", "".to_string()), + imei: rsproperties::get_or("ro.ril.oem.imei", "".to_string()), + imei2: rsproperties::get_or("ro.ril.oem.imei2", "".to_string()), + } + } +} diff --git a/src/global.rs b/src/global.rs index e966ebf..e54f477 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,6 +1,6 @@ use std::{ cell::RefCell, - sync::{Arc, LazyLock, Mutex, Once, RwLock, OnceLock}, + sync::{Arc, LazyLock, Mutex, Once, OnceLock, RwLock}, }; use rsbinder::Strong; @@ -8,9 +8,13 @@ use rsbinder::Strong; use anyhow::{Context, Result}; use crate::{ - android::hardware::security::{keymint::SecurityLevel::SecurityLevel, secureclock::ISecureClock::ISecureClock}, + android::hardware::security::secureclock::ISecureClock::ISecureClock, err, - keymaster::{async_task::AsyncTask, db::KeymasterDb, enforcements::Enforcements, gc::Gc, keymint_device::get_keymint_wrapper, super_key::SuperKeyManager}, watchdog as wd, + keymaster::{ + async_task::AsyncTask, db::KeymasterDb, enforcements::Enforcements, gc::Gc, + keymint_device::get_keymint_wrapper, super_key::SuperKeyManager, + }, + watchdog as wd, }; static DB_INIT: Once = Once::new(); @@ -23,28 +27,16 @@ static GC: LazyLock> = LazyLock::new(|| { Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || { ( Box::new(|uuid, blob| { - let security_level = u128::from_be_bytes(uuid.0) as u32; - let security_level = match security_level { - 100 => SecurityLevel::KEYSTORE, - 1 => SecurityLevel::TRUSTED_ENVIRONMENT, - 2 => SecurityLevel::STRONGBOX, - _ => { - return Err(anyhow::anyhow!( - "Invalid security level {security_level} in UUID" - )) - } - }; - + let security_level = uuid.to_security_level().unwrap(); + let km_dev = get_keymint_wrapper(security_level).unwrap(); let _wp = wd::watch("invalidate key closure: calling IKeyMintDevice::deleteKey"); - km_dev.delete_Key(blob) + km_dev + .delete_Key(blob) .map_err(|e| anyhow::anyhow!("Failed to delete key blob: {e}")) .context(err!("Trying to invalidate key blob.")) }), - KeymasterDb::new( - None - ) - .expect("Failed to open database"), + KeymasterDb::new(None).expect("Failed to open database"), SUPER_KEY.clone(), ) })) diff --git a/src/keymint/attest.rs b/src/keybox.rs similarity index 84% rename from src/keymint/attest.rs rename to src/keybox.rs index 1ce2066..0bf9feb 100644 --- a/src/keymint/attest.rs +++ b/src/keybox.rs @@ -1,27 +1,16 @@ -// -// Copyright (C) 2022 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. - -//! Attestation keys and certificates. -//! -//! Hard-coded keys and certs copied from system/keymaster/context/soft_attestation_cert.cpp +use std::sync::RwLock; +use kmr_common::crypto::Sha256; use kmr_common::{ - crypto::ec, crypto::rsa, crypto::CurveType, crypto::KeyMaterial, wire::keymint, - wire::keymint::EcCurve, Error, + crypto::{ec, CurveType, KeyMaterial}, + km_err, Error, }; +use kmr_crypto_boring::sha256::BoringSha256; use kmr_ta::device::{RetrieveCertSigningInfo, SigningAlgorithm, SigningKeyType}; +use kmr_wire::keymint::{self, EcCurve}; +use log::debug; + +use crate::err; /// RSA attestation private key in PKCS#1 format. /// @@ -362,24 +351,33 @@ const EC_ATTEST_ROOT_CERT: &str = concat!( "a585653cad4f24a7e74daf417df1bf", ); +lazy_static::lazy_static! { + /// A `KeyBox` instance holding all the hardcoded keys and certificates. + pub static ref KEYBOX: RwLock = RwLock::new(KeyBox::new()); +} + /// Per-algorithm attestation certificate signing information. pub struct CertSignAlgoInfo { key: KeyMaterial, chain: Vec, + digest: [u8; 32], // SHA-256 of the public key } /// Certificate signing information for all asymmetric key types. -pub struct CertSignInfo { +pub struct KeyBox { rsa_info: CertSignAlgoInfo, ec_info: CertSignAlgoInfo, } -impl CertSignInfo { +impl KeyBox { /// Create a new cert signing impl. pub fn new() -> Self { - CertSignInfo { + let hasher = BoringSha256 {}; + KeyBox { rsa_info: CertSignAlgoInfo { - key: KeyMaterial::Rsa(rsa::Key(hex::decode(RSA_ATTEST_KEY).unwrap()).into()), + key: KeyMaterial::Rsa( + kmr_common::crypto::rsa::Key(hex::decode(RSA_ATTEST_KEY).unwrap()).into(), + ), chain: vec![ keymint::Certificate { encoded_certificate: hex::decode(RSA_ATTEST_CERT).unwrap(), @@ -388,6 +386,11 @@ impl CertSignInfo { encoded_certificate: hex::decode(RSA_ATTEST_ROOT_CERT).unwrap(), }, ], + digest: { + hasher + .hash(hex::decode(RSA_ATTEST_CERT).unwrap().as_slice()) + .unwrap() + }, }, ec_info: CertSignAlgoInfo { key: KeyMaterial::Ec( @@ -403,23 +406,93 @@ impl CertSignInfo { encoded_certificate: hex::decode(EC_ATTEST_ROOT_CERT).unwrap(), }, ], + digest: { + hasher + .hash(hex::decode(EC_ATTEST_CERT).unwrap().as_slice()) + .unwrap() + }, }, } } + + pub fn update_rsa_keybox(&mut self, key: Vec, chain: Vec) { + debug!("Updating RSA keybox: {}", err!()); + self.rsa_info.key = KeyMaterial::Rsa(kmr_common::crypto::rsa::Key(key).into()); + self.rsa_info.chain = chain; + self.rsa_info.digest = BoringSha256 {} + .hash(self.rsa_info.chain[0].encoded_certificate.as_slice()) + .unwrap(); + } + + pub fn update_ec_keybox(&mut self, key: Vec, chain: Vec) { + debug!("Updating EC keybox: {}", err!()); + self.ec_info.key = KeyMaterial::Ec( + EcCurve::P256, + CurveType::Nist, + ec::Key::P256(ec::NistKey(key)).into(), + ); + self.ec_info.chain = chain; + self.ec_info.digest = BoringSha256 {} + .hash(self.ec_info.chain[0].encoded_certificate.as_slice()) + .unwrap(); + } + + pub fn get_ec_key_digest(&self) -> [u8; 32] { + self.ec_info.digest + } } -impl RetrieveCertSigningInfo for CertSignInfo { +pub struct KeyboxManager; + +impl RetrieveCertSigningInfo for KeyboxManager { fn signing_key(&self, key_type: SigningKeyType) -> Result { Ok(match key_type.algo_hint { - SigningAlgorithm::Rsa => self.rsa_info.key.clone(), - SigningAlgorithm::Ec => self.ec_info.key.clone(), + SigningAlgorithm::Rsa => { + let keybox = KEYBOX.read().map_err(|e| { + km_err!( + AttestationKeysNotProvisioned, + "Failed to read KEYBOX: {}", + e + ) + })?; + keybox.rsa_info.key.clone() + } + SigningAlgorithm::Ec => { + debug!("Retrieving EC signing key from KEYBOX"); + let keybox = KEYBOX.read().map_err(|e| { + km_err!( + AttestationKeysNotProvisioned, + "Failed to read KEYBOX: {}", + e + ) + })?; + keybox.ec_info.key.clone() + } }) } fn cert_chain(&self, key_type: SigningKeyType) -> Result, Error> { Ok(match key_type.algo_hint { - SigningAlgorithm::Rsa => self.rsa_info.chain.clone(), - SigningAlgorithm::Ec => self.ec_info.chain.clone(), + SigningAlgorithm::Rsa => { + let keybox = KEYBOX.read().map_err(|e| { + km_err!( + AttestationKeysNotProvisioned, + "Failed to read KEYBOX: {}", + e + ) + })?; + keybox.rsa_info.chain.clone() + } + SigningAlgorithm::Ec => { + let keybox = KEYBOX.read().map_err(|e| { + km_err!( + AttestationKeysNotProvisioned, + "Failed to read KEYBOX: {}", + e + ) + })?; + keybox.ec_info.chain.clone() + } }) } } diff --git a/src/keymaster/apex.rs b/src/keymaster/apex.rs index 68187a1..a9b0f5a 100644 --- a/src/keymaster/apex.rs +++ b/src/keymaster/apex.rs @@ -1,6 +1,9 @@ use std::cmp::Ordering; -use der::{DerOrd, Sequence, asn1::OctetString}; +use der::{ + asn1::{OctetString, SetOfVec}, + DerOrd, Encode, Sequence, +}; #[derive(Sequence, Debug)] pub struct ApexModuleInfo { @@ -18,4 +21,8 @@ impl DerOrd for ApexModuleInfo { fn der_cmp(&self, other: &Self) -> std::result::Result { self.package_name.der_cmp(&other.package_name) } -} \ No newline at end of file +} + +pub fn encode_module_info(module_info: Vec) -> Result, der::Error> { + SetOfVec::::from_iter(module_info.into_iter())?.to_der() +} diff --git a/src/keymaster/async_task.rs b/src/keymaster/async_task.rs index eb432ec..004bb26 100644 --- a/src/keymaster/async_task.rs +++ b/src/keymaster/async_task.rs @@ -41,18 +41,24 @@ pub struct Shelf(HashMap>); impl Shelf { /// Get a reference to the shelved data of type T. Returns Some if the data exists. pub fn get_downcast_ref(&self) -> Option<&T> { - self.0.get(&TypeId::of::()).and_then(|v| v.downcast_ref::()) + self.0 + .get(&TypeId::of::()) + .and_then(|v| v.downcast_ref::()) } /// Get a mutable reference to the shelved data of type T. If a T was inserted using put, /// get_mut, or get_or_put_with. pub fn get_downcast_mut(&mut self) -> Option<&mut T> { - self.0.get_mut(&TypeId::of::()).and_then(|v| v.downcast_mut::()) + self.0 + .get_mut(&TypeId::of::()) + .and_then(|v| v.downcast_mut::()) } /// Remove the entry of the given type and returns the stored data if it existed. pub fn remove_downcast_ref(&mut self) -> Option { - self.0.remove(&TypeId::of::()).and_then(|v| v.downcast::().ok().map(|b| *b)) + self.0 + .remove(&TypeId::of::()) + .and_then(|v| v.downcast::().ok().map(|b| *b)) } /// Puts data `v` on the shelf. If there already was an entry of type T it is returned. diff --git a/src/keymaster/database/utils.rs b/src/keymaster/database/utils.rs index f60787a..b01a3e2 100644 --- a/src/keymaster/database/utils.rs +++ b/src/keymaster/database/utils.rs @@ -1,7 +1,14 @@ use anyhow::{Context, Result}; use rusqlite::{types::FromSql, Row, Rows}; -use crate::{android::system::keystore2::{Domain::Domain, KeyDescriptor::KeyDescriptor}, err, keymaster::{db::{KeyType, KeymasterDb}, error::KsError}}; +use crate::{ + android::system::keystore2::{Domain::Domain, KeyDescriptor::KeyDescriptor}, + err, + keymaster::{ + db::{KeyType, KeymasterDb}, + error::KsError, + }, +}; // Takes Rows as returned by a query call on prepared statement. // Extracts exactly one row with the `row_extractor` and fails if more @@ -97,7 +104,6 @@ pub fn list_key_entries( .list_past_alias(domain, namespace, KeyType::Client, start_past_alias) .context(err!("Trying to list keystore database past alias."))?; - let safe_amount_to_return = estimate_safe_amount_to_return( domain, namespace, diff --git a/src/keymaster/db.rs b/src/keymaster/db.rs index 49b6fef..3f38278 100644 --- a/src/keymaster/db.rs +++ b/src/keymaster/db.rs @@ -7,6 +7,7 @@ use std::{ }; use anyhow::{anyhow, Context, Result}; +use log::{debug, info}; use rand::random; use rusqlite::{ params, params_from_iter, @@ -2022,6 +2023,57 @@ impl KeymasterDb { }) .context(err!()) } + + pub fn terminate_uuid(&mut self, km_uuid: &Uuid) -> Result<()> { + info!("Terminating all keys created by UUID {:0x?}", km_uuid); + let _wp = wd::watch("KeystoreDB::terminate_uuid"); + + self.with_transaction(Immediate("TX_terminate_uuid"), |tx| { + let mut stmt = tx + .prepare( + "SELECT id FROM persistent.keyentry + WHERE km_uuid = ?;", + ) + .context( + "Failed to prepare the query to find the keys created by the given UUID.", + )?; + + let mut rows = stmt + .query(params![km_uuid]) + .context("Failed to query the keys created by the given UUID.")?; + + let mut key_ids: Vec = Vec::new(); + utils::with_rows_extract_all(&mut rows, |row| { + key_ids.push(row.get(0).context("Failed to read key id.")?); + Ok(()) + }) + .context("Failed to extract rows.")?; + info!("Found {} keys to terminate.", key_ids.len()); + + let mut notify_gc = false; + for key_id in key_ids { + debug!("Terminating key {:0x?}", key_id); + let result = tx + .execute( + "UPDATE persistent.keyentry SET state = ? WHERE id = ?;", + params![KeyLifeCycle::Unreferenced, key_id], + ) + .context(err!("Failed to set alias."))?; + if result != 1 { + return Err(KsError::sys()).context(err!( + "Expected to update a single entry but instead updated {}.", + result + )); + } + + debug!("Marked key {:0x?} unreferenced", key_id); + notify_gc = + Self::mark_unreferenced(tx, key_id).context("In terminate_uuid.")? || notify_gc; + } + Ok(()).do_gc(notify_gc) + }) + .context(err!()) + } } /// Database representation of the monotonic time retrieved from the system call clock_gettime with @@ -2330,7 +2382,40 @@ pub struct Uuid(pub(crate) [u8; 16]); impl From for Uuid { fn from(sec_level: SecurityLevel) -> Self { - Self((sec_level.0 as u128).to_be_bytes()) + let digest = { crate::keybox::KEYBOX.read().unwrap().get_ec_key_digest() }; // avoid deadlock + + let mut uuid_bytes = [0u8; 16]; + + // First 12 bytes: digest (truncate or pad as needed) + let digest_len = digest.len().min(12); + uuid_bytes[..digest_len].copy_from_slice(&digest[..digest_len]); + + // Last 4 bytes: security level in big-endian + let sec_level_bytes = (sec_level.0 as u32).to_be_bytes(); + uuid_bytes[12..16].copy_from_slice(&sec_level_bytes); + + Self(uuid_bytes) + } +} + +impl Uuid { + /// Convert UUID back to SecurityLevel by reading the last 4 bytes + pub fn to_security_level(&self) -> Option { + // Read the last 4 bytes as big-endian u32 + let mut sec_level_bytes = [0u8; 4]; + sec_level_bytes.copy_from_slice(&self.0[12..16]); + let sec_level_value = u32::from_be_bytes(sec_level_bytes); + + // Validate that it's one of the allowed values: 0, 1, 2, 100 + match sec_level_value { + 0 | 1 | 2 | 100 => Some(SecurityLevel(sec_level_value as i32)), + _ => None, + } + } + + /// Get the digest portion (first 12 bytes) of the UUID + pub fn get_digest(&self) -> &[u8] { + &self.0[..12] } } diff --git a/src/keymaster/gc.rs b/src/keymaster/gc.rs index 363b66c..2cdc217 100644 --- a/src/keymaster/gc.rs +++ b/src/keymaster/gc.rs @@ -70,15 +70,22 @@ impl Gc { notified, }); }); - Self { async_task, notified } + Self { + async_task, + notified, + } } /// Notifies the key garbage collector to iterate through orphaned and superseded blobs and /// attempts their deletion. We only process one key at a time and then schedule another /// attempt by queueing it in the async_task (low priority) queue. pub fn notify_gc(&self) { - if let Ok(0) = self.notified.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) { - self.async_task.queue_lo(|shelf| shelf.get_downcast_mut::().unwrap().step()) + if let Ok(0) = self + .notified + .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) + { + self.async_task + .queue_lo(|shelf| shelf.get_downcast_mut::().unwrap().step()) } } } @@ -110,7 +117,12 @@ impl GcInternal { self.superseded_blobs = blobs; } - if let Some(SupersededBlob { blob_id, blob, metadata }) = self.superseded_blobs.pop() { + if let Some(SupersededBlob { + blob_id, + blob, + metadata, + }) = self.superseded_blobs.pop() + { // Add the next blob_id to the deleted blob ids list. So it will be // removed from the database regardless of whether the following // succeeds or not. @@ -144,7 +156,8 @@ impl GcInternal { if !self.deleted_blob_ids.is_empty() { if let Some(at) = self.async_task.upgrade() { if let Ok(0) = - self.notified.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) + self.notified + .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) { at.queue_lo(move |shelf| { shelf.get_downcast_mut::().unwrap().step() diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index f25d556..d8e0fb0 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -14,7 +14,7 @@ //! Provide the [`KeyMintDevice`] wrapper for operating directly on a KeyMint device. -use std::sync::OnceLock; +use std::sync::{OnceLock, RwLock}; use crate::android::hardware::security::keymint::IKeyMintOperation::BnKeyMintOperation; use crate::android::hardware::security::keymint::{ @@ -26,7 +26,9 @@ use crate::android::hardware::security::keymint::{ use crate::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, }; -use crate::global::AID_KEYSTORE; +use crate::config::CONFIG; +use crate::global::{AID_KEYSTORE, DB}; +use crate::keymaster::apex::encode_module_info; use crate::keymaster::db::Uuid; use crate::keymaster::error::{map_binder_status, map_ks_error, map_ks_result}; use crate::keymaster::utils::{key_creation_result_to_aidl, key_param_to_aidl}; @@ -46,17 +48,19 @@ use crate::{ watchdog as wd, }; use anyhow::{Context, Ok, Result}; +use kmr_common::crypto::Rng; +use kmr_common::crypto::Sha256; use kmr_crypto_boring::ec::BoringEc; use kmr_crypto_boring::hmac::BoringHmac; use kmr_crypto_boring::rng::BoringRng; use kmr_crypto_boring::rsa::BoringRsa; -use kmr_common::crypto::Rng; +use kmr_crypto_boring::sha256::BoringSha256; use kmr_ta::device::CsrSigningAlgorithm; use kmr_ta::{HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; use kmr_wire::keymint::{AttestationKey, KeyParam}; use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; use kmr_wire::*; -use log::error; +use log::{error, warn}; use rsbinder::{ExceptionCode, Interface, Status, Strong}; /// Wrapper for operating directly on a KeyMint device. @@ -71,7 +75,7 @@ use rsbinder::{ExceptionCode, Interface, Status, Strong}; pub struct KeyMintDevice { km_dev: KeyMintWrapper, version: i32, - km_uuid: Uuid, + km_uuid: RwLock, security_level: SecurityLevel, } @@ -89,12 +93,12 @@ impl KeyMintDevice { /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] pub fn get(security_level: SecurityLevel) -> Result { - let km_dev = get_keymint_device(security_level) - .context(err!("get_keymint_device failed: {:?}", security_level))?; + let km_dev = KeyMintWrapper::new(security_level) + .expect(err!("Failed to init strongbox wrapper").as_str()); let hw_info = km_dev.get_hardware_info().unwrap(); - let km_uuid: Uuid = Default::default(); - let wrapper = KeyMintWrapper::new(security_level).unwrap(); + let km_uuid = RwLock::new(Uuid::from(security_level)); + let wrapper: KeyMintWrapper = KeyMintWrapper::new(security_level).unwrap(); Ok(KeyMintDevice { km_dev: wrapper, version: hw_info.version_number, @@ -114,6 +118,21 @@ impl KeyMintDevice { }) } + pub fn uuid(&self) -> Uuid { + *self.km_uuid.read().unwrap() + } + + pub fn terminate_uuid(&mut self) -> Result<()> { + DB.with(|db| { + let mut db = db.borrow_mut(); + db.terminate_uuid(&self.km_uuid.read().unwrap()) + .context(err!("terminate_uuid failed")) + })?; + + self.km_uuid = RwLock::new(Uuid::from(self.security_level)); + Ok(()) + } + /// Returns the version of the underlying KeyMint/KeyMaster device. pub fn version(&self) -> i32 { self.version @@ -147,7 +166,7 @@ impl KeyMintDevice { let mut key_metadata = KeyMetaData::new(); key_metadata.add(KeyMetaEntry::CreationDate(creation_date)); let mut blob_metadata = BlobMetaData::new(); - blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); + blob_metadata.add(BlobMetaEntry::KmUuid(*self.km_uuid.read().unwrap())); db.store_new_key( key_desc, @@ -156,7 +175,7 @@ impl KeyMintDevice { &BlobInfo::new(&creation_result.keyBlob, &blob_metadata), &CertificateInfo::new(None, None), &key_metadata, - &self.km_uuid, + &*self.km_uuid.read().unwrap(), ) .context(err!("store_new_key failed"))?; Ok(()) @@ -223,7 +242,7 @@ impl KeyMintDevice { let key_blob = key_entry .take_key_blob_info() .and_then(|(key_blob, blob_metadata)| { - if Some(&self.km_uuid) == blob_metadata.km_uuid() { + if Some(*self.km_uuid.read().unwrap()) == blob_metadata.km_uuid().copied() { Some(key_blob) } else { None @@ -295,7 +314,7 @@ impl KeyMintDevice { f, |upgraded_blob| { let mut new_blob_metadata = BlobMetaData::new(); - new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid)); + new_blob_metadata.add(BlobMetaEntry::KmUuid(*self.km_uuid.read().unwrap())); db.set_blob( key_id_guard, @@ -354,16 +373,13 @@ impl KeyMintDevice { use std::sync::Mutex; -static KM_STRONGBOX: OnceLock> = OnceLock::new(); - -static KM_TEE: OnceLock> = OnceLock::new(); - static KM_WRAPPER_STRONGBOX: OnceLock> = OnceLock::new(); static KM_WRAPPER_TEE: OnceLock> = OnceLock::new(); pub struct KeyMintWrapper { security_level: SecurityLevel, + keymint: Mutex, } unsafe impl Sync for KeyMintWrapper {} @@ -394,9 +410,7 @@ impl IKeyMintDevice for KeyMintWrapper { })?, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -433,8 +447,8 @@ impl IKeyMintDevice for KeyMintWrapper { crate::android::hardware::security::keymint::KeyMintHardwareInfo::KeyMintHardwareInfo, Status, > { - let hardware_info: keymint::KeyMintHardwareInfo = get_keymint_device(self.security_level) - .unwrap() + let hardware_info: keymint::KeyMintHardwareInfo = self.keymint + .lock().unwrap() .get_hardware_info() .unwrap(); @@ -467,9 +481,7 @@ impl IKeyMintDevice for KeyMintWrapper { data: data.to_vec(), }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -515,8 +527,7 @@ impl IKeyMintDevice for KeyMintWrapper { key_params: key_parameters, attestation_key, }); - let result = get_keymint_device(self.security_level) - .unwrap() + let result = self.keymint.lock().unwrap() .process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -581,9 +592,7 @@ impl IKeyMintDevice for KeyMintWrapper { key_data: key_data.to_vec(), attestation_key, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -631,9 +640,7 @@ impl IKeyMintDevice for KeyMintWrapper { biometric_sid, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -668,9 +675,7 @@ impl IKeyMintDevice for KeyMintWrapper { upgrade_params, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -691,9 +696,7 @@ impl IKeyMintDevice for KeyMintWrapper { fn deleteKey(&self, key_blob: &[u8]) -> rsbinder::status::Result<()> { let key_blob = key_blob.to_vec(); let req = PerformOpReq::DeviceDeleteKey(DeleteKeyRequest { key_blob }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -705,9 +708,7 @@ impl IKeyMintDevice for KeyMintWrapper { fn deleteAllKeys(&self) -> rsbinder::status::Result<()> { let req = PerformOpReq::DeviceDeleteAllKeys(DeleteAllKeysRequest {}); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -719,9 +720,7 @@ impl IKeyMintDevice for KeyMintWrapper { fn destroyAttestationIds(&self) -> rsbinder::status::Result<()> { let req = PerformOpReq::DeviceDestroyAttestationIds(DestroyAttestationIdsRequest {}); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -743,9 +742,7 @@ impl IKeyMintDevice for KeyMintWrapper { fn earlyBootEnded(&self) -> rsbinder::status::Result<()> { let req = PerformOpReq::DeviceEarlyBootEnded(EarlyBootEndedRequest {}); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -763,9 +760,7 @@ impl IKeyMintDevice for KeyMintWrapper { storage_key_blob: storage_key_blob.to_vec(), }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -796,9 +791,7 @@ impl IKeyMintDevice for KeyMintWrapper { app_data: app_data.to_vec(), }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -846,9 +839,7 @@ impl IKeyMintDevice for KeyMintWrapper { fn getRootOfTrustChallenge(&self) -> rsbinder::status::Result<[u8; 16]> { let req = PerformOpReq::GetRootOfTrustChallenge(GetRootOfTrustChallengeRequest {}); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -870,9 +861,7 @@ impl IKeyMintDevice for KeyMintWrapper { challenge: *challenge, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -895,9 +884,7 @@ impl IKeyMintDevice for KeyMintWrapper { root_of_trust: root_of_trust.to_vec(), }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -920,9 +907,7 @@ impl IKeyMintDevice for KeyMintWrapper { info: additional_info, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Status::new_service_specific_error(result.error_code, None)); @@ -933,17 +918,21 @@ impl IKeyMintDevice for KeyMintWrapper { } impl KeyMintWrapper { - fn new(security_level: SecurityLevel) -> Result { + pub fn new(security_level: SecurityLevel) -> Result { Ok(KeyMintWrapper { security_level: security_level.clone(), + keymint: Mutex::new(init_keymint_ta(security_level)?), }) } - pub fn get_hardware_info( - &self, - ) -> Result { - get_keymint_device(self.security_level) - .unwrap() + pub fn reset_keymint_ta(&self) -> Result<()> { + let mut keymint = self.keymint.lock().unwrap(); + *keymint = init_keymint_ta(self.security_level.clone())?; + Ok(()) + } + + pub fn get_hardware_info(&self) -> Result { + self.keymint.lock().unwrap() .get_hardware_info() .map_err(|_| Error::Km(ErrorCode::UNKNOWN_ERROR)) } @@ -982,9 +971,7 @@ impl KeyMintWrapper { auth_token: hardware_auth_token, timestamp_token: timestamp_token, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Error::Binder( ExceptionCode::ServiceSpecific, @@ -1033,9 +1020,7 @@ impl KeyMintWrapper { auth_token: hardware_auth_token, timestamp_token: timestamp_token, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Error::Binder( ExceptionCode::ServiceSpecific, @@ -1091,9 +1076,7 @@ impl KeyMintWrapper { timestamp_token: timestamp_token, confirmation_token: confirmation_token, }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Error::Binder( ExceptionCode::ServiceSpecific, @@ -1110,9 +1093,7 @@ impl KeyMintWrapper { pub fn op_abort(&self, op_handle: i64) -> Result<(), Error> { let req = PerformOpReq::OperationAbort(AbortRequest { op_handle }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Error::Binder( ExceptionCode::ServiceSpecific, @@ -1132,9 +1113,7 @@ impl KeyMintWrapper { let req = PerformOpReq::DeviceDeleteKey(DeleteKeyRequest { key_blob: key_blob.to_vec(), }); - let result = get_keymint_device(self.security_level) - .unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if result.error_code != 0 { return Err(Error::Binder( @@ -1227,14 +1206,9 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { let dev = kmr_ta::device::Implementation { keys, - // Cuttlefish has `remote_provisioning.tee.rkp_only=1` so don't support batch signing - // of keys. This can be reinstated with: - // ``` - // sign_info: Some(kmr_ta_nonsecure::attest::CertSignInfo::new()), - // ``` - sign_info: Some(Box::new(crate::keymint::attest::CertSignInfo::new())), + sign_info: Some(Box::new(crate::keybox::KeyboxManager {})), // HAL populates attestation IDs from properties. - attest_ids: None, + attest_ids: Some(Box::new(crate::att_mgr::AttestationIdMgr {})), sdd_mgr, // `BOOTLOADER_ONLY` keys not supported. bootloader: Box::new(kmr_ta::device::BootloaderDone), @@ -1256,59 +1230,72 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { let mut vb_key = vec![0u8; 32]; rng.fill_bytes(&mut vb_key); + let config = CONFIG.read().unwrap(); + + let patch_level = config.trust.security_patch.replace("-", ""); + let patch_level = patch_level.parse::().unwrap_or(20250605); + let boot_patchlevel = patch_level; + let os_patchlevel = patch_level / 100; + let req = PerformOpReq::SetBootInfo(kmr_wire::SetBootInfoRequest { - verified_boot_state: 0, // Verified - verified_boot_hash: vb_hash, - verified_boot_key: vb_key, - device_boot_locked: true, - boot_patchlevel: 20250605, + verified_boot_state: if config.trust.verified_boot_state { + 0 + } else { + 2 + }, + verified_boot_hash: config.trust.vb_hash.clone().to_vec(), + verified_boot_key: config.trust.vb_key.clone().to_vec(), + device_boot_locked: config.trust.device_locked, + boot_patchlevel, }); let resp = ta.process_req(req); if resp.error_code != 0 { - return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)) - .context(err!("Failed to set boot info")); + return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)).context(err!("Failed to set boot info")); } + let req = PerformOpReq::SetHalInfo(kmr_wire::SetHalInfoRequest { - os_version: 35, - os_patchlevel: 202506, - vendor_patchlevel: 202506, + os_version: config.trust.os_version as u32, + os_patchlevel, + vendor_patchlevel: os_patchlevel, }); let resp = ta.process_req(req); if resp.error_code != 0 { - return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)) - .context(err!("Failed to set HAL info")); + return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)).context(err!("Failed to set HAL info")); } - Ok(ta) -} + let module_hash = + crate::global::ENCODED_MODULE_INFO.get_or_try_init(|| -> Result, anyhow::Error> { + let apex_info = crate::plat::utils::get_apex_module_info()?; -pub fn get_keymint_device<'a>( - security_level: SecurityLevel, -) -> Result> { - match security_level { - SecurityLevel::STRONGBOX => { - let strongbox = KM_STRONGBOX.get_or_init(|| { - Mutex::new( - init_keymint_ta(security_level) - .expect(err!("Failed to init strongbox wrapper").as_str()), - ) - }); - Ok(strongbox.lock().expect("Failed to lock KM_STRONGBOX")) - } - SecurityLevel::TRUSTED_ENVIRONMENT => { - let tee = KM_TEE.get_or_init(|| { - Mutex::new( - init_keymint_ta(security_level) - .expect(err!("Failed to init tee wrapper").as_str()), - ) - }); - Ok(tee.lock().expect("Failed to lock KM_TEE")) + let encoding = encode_module_info(apex_info) + .map_err(|_| anyhow::anyhow!("Failed to encode module info."))?; + + let sha256 = BoringSha256 {}; + + let hash = sha256 + .hash(&encoding) + .map_err(|_| anyhow::anyhow!("Failed to hash module info."))?; + + Ok(hash.to_vec()) + }); + + if let Result::Ok(hash) = module_hash { + let module_hash = KeyParam::ModuleHash(hash.to_vec()); + let req = PerformOpReq::SetAdditionalAttestationInfo( + kmr_wire::SetAdditionalAttestationInfoRequest { + info: vec![module_hash], + }, + ); + let resp = ta.process_req(req); + if resp.error_code != 0 { + return Err(Error::Km(ErrorCode::UNKNOWN_ERROR)) + .context(err!("Failed to set additional attestation info")); } - SecurityLevel::SOFTWARE => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) - .context(err!("Software KeyMint not supported")), - _ => Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) - .context(err!("Unknown security level")), + } else { + warn!("Failed to get module hash: {:?}", module_hash.err()); } + + Ok(ta) } pub fn get_keymint_wrapper<'a>( diff --git a/src/keymaster/mod.rs b/src/keymaster/mod.rs index 027a6e2..f0af887 100644 --- a/src/keymaster/mod.rs +++ b/src/keymaster/mod.rs @@ -1,6 +1,6 @@ pub mod apex; -pub mod attestation_key_utils; pub mod async_task; +pub mod attestation_key_utils; pub mod boot_key; pub mod crypto; pub mod database; @@ -15,6 +15,6 @@ pub mod metrics_store; pub mod operation; pub mod permission; pub mod security_level; -pub mod super_key; pub mod service; +pub mod super_key; pub mod utils; diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 694e0c0..36eb93b 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -26,13 +26,14 @@ use crate::{ Uuid, }, error::{KsError, into_logged_binder, map_binder_status}, - keymint_device::get_keymint_wrapper, + keymint_device::{KeyMintWrapper}, metrics_store::log_key_creation_event_stats, operation::{KeystoreOperation, LoggingInfo, OperationDb}, super_key::{KeyBlob, SuperKeyManager}, utils::{key_characteristics_to_internal, key_parameters_to_authorizations, log_params}, }, - plat::utils::multiuser_get_user_id, top::qwq2333::ohmykeymint::{CallerInfo::CallerInfo, IOhMySecurityLevel::IOhMySecurityLevel}, + plat::utils::multiuser_get_user_id, + top::qwq2333::ohmykeymint::{CallerInfo::CallerInfo, IOhMySecurityLevel::IOhMySecurityLevel}, }; use crate::keymaster::key_parameter::KeyParameter as KsKeyParam; @@ -50,26 +51,28 @@ static ZERO_BLOB_32: &[u8] = &[0; 32]; pub struct KeystoreSecurityLevel { security_level: SecurityLevel, + km_wrapper: KeyMintWrapper, hw_info: KeyMintHardwareInfo, km_uuid: Uuid, operation_db: OperationDb, } impl KeystoreSecurityLevel { - pub fn new(security_level: SecurityLevel) -> Result<(Self, Uuid)> { - let km_uuid = Uuid::from(security_level); - - let hw_info = get_keymint_wrapper(security_level) - .unwrap() + pub fn new(security_level: SecurityLevel, km_uuid: Uuid) -> Result { + let km_wrapper = KeyMintWrapper::new(security_level) + .expect(err!("Failed to init strongbox wrapper").as_str()); + + let hw_info = km_wrapper .get_hardware_info() .context(err!("Failed to get hardware info."))?; - Ok((KeystoreSecurityLevel { + Ok(KeystoreSecurityLevel { security_level, + km_wrapper, hw_info, km_uuid, operation_db: OperationDb::new(), - }, km_uuid)) + }) } fn watch_millis(&self, id: &'static str, millis: u64) -> Option { @@ -82,6 +85,10 @@ impl KeystoreSecurityLevel { wd::watch_millis_with(id, wd::DEFAULT_TIMEOUT_MS, sec_level) } + fn get_keymint_wrapper(&self) -> &KeyMintWrapper { + &self.km_wrapper + } + fn store_upgraded_keyblob( key_id_guard: KeyIdGuard, km_uuid: Option, @@ -166,9 +173,7 @@ impl KeystoreSecurityLevel { value: KeyParameterValue::Blob(aaid_ok), }); } - Err(e) => { - return Err(anyhow!(e)).context(err!("Attestation ID retrieval error.")) - } + Err(e) => return Err(anyhow!(e)).context(err!("Attestation ID retrieval error.")), } } @@ -203,9 +208,18 @@ impl KeystoreSecurityLevel { // If we are generating/importing an asymmetric key, we need to make sure // that NOT_BEFORE and NOT_AFTER are present. match params.iter().find(|kp| kp.tag == Tag::ALGORITHM) { - Some(KeyParameter { tag: _, value: KeyParameterValue::Algorithm(Algorithm::RSA) }) - | Some(KeyParameter { tag: _, value: KeyParameterValue::Algorithm(Algorithm::EC) }) => { - if !params.iter().any(|kp| kp.tag == Tag::CERTIFICATE_NOT_BEFORE) { + Some(KeyParameter { + tag: _, + value: KeyParameterValue::Algorithm(Algorithm::RSA), + }) + | Some(KeyParameter { + tag: _, + value: KeyParameterValue::Algorithm(Algorithm::EC), + }) => { + if !params + .iter() + .any(|kp| kp.tag == Tag::CERTIFICATE_NOT_BEFORE) + { result.push(KeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0), @@ -452,8 +466,7 @@ impl KeystoreSecurityLevel { ), 5000, // Generate can take a little longer. ); - let result = get_keymint_wrapper(self.security_level) - .unwrap() + let result = self.get_keymint_wrapper() .generateKey(¶ms, attest_key.as_ref()); map_binder_status(result) }, @@ -472,8 +485,7 @@ impl KeystoreSecurityLevel { ), 5000, // Generate can take a little longer. ); - get_keymint_wrapper(self.security_level) - .unwrap() + self.get_keymint_wrapper() .generateKey(¶ms, None) } .context(err!( @@ -542,7 +554,7 @@ impl KeystoreSecurityLevel { }) .context(err!())?; - let km_dev = get_keymint_wrapper(self.security_level).unwrap(); + let km_dev = self.get_keymint_wrapper(); let creation_result = map_binder_status({ let _wp = self.watch("KeystoreSecurityLevel::import_key: calling IKeyMintDevice::importKey."); @@ -678,7 +690,7 @@ impl KeystoreSecurityLevel { let _wp = self.watch( "KeystoreSecurityLevel::import_wrapped_key: calling IKeyMintDevice::importWrappedKey.", ); - let km_dev = get_keymint_wrapper(self.security_level).unwrap(); + let km_dev = self.get_keymint_wrapper(); let creation_result = map_binder_status(km_dev.importWrappedKey( wrapped_data, wrapping_blob, @@ -714,7 +726,7 @@ impl KeystoreSecurityLevel { // check_key_permission(KeyPerm::ConvertStorageKeyToEphemeral, storage_key, &None) // .context(err!("Check permission"))?; - let km_dev = get_keymint_wrapper(self.security_level).unwrap(); + let km_dev = self.get_keymint_wrapper(); let res = { let _wp = self.watch(concat!( "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", @@ -878,7 +890,7 @@ impl KeystoreSecurityLevel { let _wp = self.watch( "KeystoreSecurityLevel::create_operation: calling IKeyMintDevice::begin", ); - let km_dev = get_keymint_wrapper(self.security_level).unwrap(); + let km_dev = self.get_keymint_wrapper(); km_dev.begin( purpose, blob, @@ -974,7 +986,7 @@ impl KeystoreSecurityLevel { // check_key_permission(KeyPerm::Delete, key, &None) // .context(err!("delete_key: Checking delete permissions"))?; - let km_dev = get_keymint_wrapper(self.security_level).unwrap(); + let km_dev = self.get_keymint_wrapper(); { let _wp = self.watch("KeystoreSecuritylevel::delete_key: calling IKeyMintDevice::deleteKey"); @@ -1106,7 +1118,9 @@ impl IOhMySecurityLevel for KeystoreSecurityLevel { log_key_creation_event_stats(self.security_level, params, &result); debug!( "generateKey: calling uid: {}, result: {:02x?}", - ctx.is_some().then(|| ctx.unwrap().callingUid).unwrap_or(CallingContext::default().uid.into()), + ctx.is_some() + .then(|| ctx.unwrap().callingUid) + .unwrap_or(CallingContext::default().uid.into()), result ); result.map_err(into_logged_binder) @@ -1126,7 +1140,9 @@ impl IOhMySecurityLevel for KeystoreSecurityLevel { log_key_creation_event_stats(self.security_level, params, &result); debug!( "importKey: calling uid: {}, result: {:?}", - ctx.is_some().then(|| ctx.unwrap().callingUid).unwrap_or(CallingContext::default().uid.into()), + ctx.is_some() + .then(|| ctx.unwrap().callingUid) + .unwrap_or(CallingContext::default().uid.into()), result ); result.map_err(into_logged_binder) @@ -1146,7 +1162,9 @@ impl IOhMySecurityLevel for KeystoreSecurityLevel { log_key_creation_event_stats(self.security_level, params, &result); debug!( "importWrappedKey: calling uid: {}, result: {:?}", - ctx.is_some().then(|| ctx.unwrap().callingUid).unwrap_or(CallingContext::default().uid.into()), + ctx.is_some() + .then(|| ctx.unwrap().callingUid) + .unwrap_or(CallingContext::default().uid.into()), result ); result.map_err(into_logged_binder) diff --git a/src/keymaster/service.rs b/src/keymaster/service.rs index 2e2cddb..7980f12 100644 --- a/src/keymaster/service.rs +++ b/src/keymaster/service.rs @@ -16,17 +16,20 @@ //! AIDL spec. use std::collections::HashMap; +use std::sync::RwLock; use crate::android::hardware::security::keymint::ErrorCode::ErrorCode; use crate::android::system::keystore2::IKeystoreSecurityLevel::BnKeystoreSecurityLevel; use crate::android::system::keystore2::ResponseCode::ResponseCode; use crate::err; use crate::global::ENCODED_MODULE_INFO; -use crate::keymaster::apex::ApexModuleInfo; +use crate::keybox::KEYBOX; +use crate::keymaster::apex::encode_module_info; use crate::keymaster::database::utils::{count_key_entries, list_key_entries}; use crate::keymaster::db::KEYSTORE_UUID; use crate::keymaster::db::{KeyEntryLoadBits, KeyType, SubComponentType}; use crate::keymaster::error::{into_logged_binder, KsError as Error}; +use crate::keymaster::keymint_device::get_keymint_wrapper; use crate::keymaster::permission::KeyPermSet; use crate::keymaster::security_level::KeystoreSecurityLevel; use crate::keymaster::utils::key_parameters_to_authorizations; @@ -46,25 +49,31 @@ use crate::android::hardware::security::keymint::SecurityLevel::SecurityLevel; use crate::android::hardware::security::keymint::Tag::Tag; use crate::android::system::keystore2::{ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, - IKeystoreService::BnKeystoreService, IKeystoreService::IKeystoreService, - KeyDescriptor::KeyDescriptor, KeyEntryResponse::KeyEntryResponse, KeyMetadata::KeyMetadata, + IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, + KeyEntryResponse::KeyEntryResponse, KeyMetadata::KeyMetadata, }; use anyhow::{Context, Ok, Result}; -use der::asn1::SetOfVec; -use der::Encode; use kmr_common::crypto::Sha256; use kmr_crypto_boring::sha256::BoringSha256; use log::debug; use rsbinder::thread_state::CallingContext; use rsbinder::{Status, Strong}; -fn encode_module_info(module_info: Vec) -> Result, der::Error> { - SetOfVec::::from_iter(module_info.into_iter())?.to_der() +/// Implementation of the IKeystoreService. +pub struct KeystoreService { + security_levels: RwLock, +} + +impl Default for KeystoreService { + fn default() -> Self { + Self { + security_levels: RwLock::new(Default::default()), + } + } } -/// Implementation of the IKeystoreService. #[derive(Default, Clone)] -pub struct KeystoreService { +struct SecurityLevels { i_sec_level_by_uuid: HashMap>, i_osec_level_by_uuid: HashMap>, uuid_by_sec_level: HashMap, @@ -73,9 +82,9 @@ pub struct KeystoreService { impl KeystoreService { /// Create a new instance of the Keystore 2.0 service. pub fn new_native_binder() -> Result { - let mut result: Self = Default::default(); + let result: Self = Default::default(); - let (dev, uuid) = match KeystoreSecurityLevel::new(SecurityLevel::TRUSTED_ENVIRONMENT) { + match result.register_security_level(SecurityLevel::TRUSTED_ENVIRONMENT) { Result::Ok(v) => v, Err(e) => { log::error!("Failed to construct mandatory security level TEE: {e:?}"); @@ -84,45 +93,105 @@ impl KeystoreService { } }; - let dev: Strong = BnKeystoreSecurityLevel::new_binder(dev); + match result.register_security_level(SecurityLevel::STRONGBOX) { + Result::Ok(v) => v, + Err(e) => { + log::error!("Failed to construct mandatory security level StrongBox: {e:?}"); + log::error!("But we ignore this error because StrongBox is optional."); + } + }; - result.i_sec_level_by_uuid.insert(uuid, dev); - result - .uuid_by_sec_level - .insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid); + Ok(result) + } - // Strongbox is optional, so we ignore errors and turn the result into an Option. - if let Result::Ok((dev, uuid)) = KeystoreSecurityLevel::new(SecurityLevel::STRONGBOX) { - let dev: Strong = BnKeystoreSecurityLevel::new_binder(dev); - result.i_sec_level_by_uuid.insert(uuid, dev); - result - .uuid_by_sec_level - .insert(SecurityLevel::STRONGBOX, uuid); + fn register_security_level(&self, sec_level: SecurityLevel) -> Result<()> { + debug!("Registering security level {sec_level:?}"); + let uuid = Uuid::from(sec_level); + + // Check if we need to terminate old UUID and do it before acquiring write lock + let old_uuid_to_terminate = { + let security_levels = self.security_levels.read().unwrap(); + if let Some(&cur_uuid) = security_levels.uuid_by_sec_level.get(&sec_level) { + if uuid != cur_uuid { + Some(cur_uuid) + } else { + None + } + } else { + None + } + }; // Release read lock + + if let Some(cur_uuid) = old_uuid_to_terminate { + log::warn!("Security level {sec_level:?} was registered with a different UUID {cur_uuid:?}, overwriting with {uuid:?}."); + log::warn!("Terminating the old UUID from database."); + + DB.with(|db| { + log::warn!("Terminating old UUID {cur_uuid:?} from database."); + db.borrow_mut().terminate_uuid(&cur_uuid).map_err(|e| { + anyhow::anyhow!(err!("Failed to terminate old UUID {cur_uuid:?}: {e:?}")) + }) + })?; } + // Create security level instances BEFORE acquiring write lock to avoid holding lock during hardware calls + debug!("Creating security level instance (may involve hardware calls)"); + let i_sec_level_dev = match KeystoreSecurityLevel::new(sec_level, uuid) { + Result::Ok(v) => v, + Err(e) => { + log::error!("Failed to construct security level {sec_level:?}: {e:?}"); + return Err(e.context(err!("Trying to construct security level {sec_level:?}"))); + } + }; + let i_sec_level: Strong = + BnKeystoreSecurityLevel::new_binder(i_sec_level_dev); + // OhMyKeymint extension: register the same security levels also as IOhMySecurityLevel. - let (dev, _uuid) = match KeystoreSecurityLevel::new(SecurityLevel::TRUSTED_ENVIRONMENT) { + let i_osec_level_dev = match KeystoreSecurityLevel::new(sec_level, uuid) { Result::Ok(v) => v, Err(e) => { - log::error!("Failed to construct mandatory security level TEE: {e:?}"); + log::error!("Failed to construct security level {sec_level:?}: {e:?}"); log::error!("Does the device have a /default Keymaster or KeyMint instance?"); return Err(e.context(err!("Trying to construct mandatory security level TEE"))); } }; + let i_osec_level: Strong = + BnOhMySecurityLevel::new_binder(i_osec_level_dev); - let dev: Strong = BnOhMySecurityLevel::new_binder(dev); - result.i_osec_level_by_uuid.insert(uuid, dev); + // Now acquire write lock only for the minimal time needed to update the maps + debug!("Obtaining exclusive lock to register security level"); + let mut security_levels = self.security_levels.write().unwrap(); + debug!("Obtained exclusive lock to register security level"); - if let Result::Ok((dev, uuid)) = KeystoreSecurityLevel::new(SecurityLevel::STRONGBOX) { - let dev: Strong = BnOhMySecurityLevel::new_binder(dev); - result.i_osec_level_by_uuid.insert(uuid, dev); + if security_levels.uuid_by_sec_level.contains_key(&sec_level) { + // Unregister if already registered + let cur_uuid = security_levels + .uuid_by_sec_level + .get(&sec_level) + .cloned() + .unwrap(); + security_levels.i_sec_level_by_uuid.remove(&cur_uuid); + security_levels.i_osec_level_by_uuid.remove(&cur_uuid); + security_levels.uuid_by_sec_level.remove(&sec_level); + log::warn!("Security level {sec_level:?} was already registered, overwriting."); } - Ok(result) + security_levels + .i_sec_level_by_uuid + .insert(uuid, i_sec_level); + security_levels + .i_osec_level_by_uuid + .insert(uuid, i_osec_level); + security_levels.uuid_by_sec_level.insert(sec_level, uuid); + + Ok(()) } fn uuid_to_sec_level(&self, uuid: &Uuid) -> SecurityLevel { - self.uuid_by_sec_level + self.security_levels + .read() + .unwrap() + .uuid_by_sec_level .iter() .find(|(_, v)| **v == *uuid) .map(|(s, _)| *s) @@ -130,7 +199,8 @@ impl KeystoreService { } fn get_i_sec_level_by_uuid(&self, uuid: &Uuid) -> Result> { - if let Some(dev) = self.i_sec_level_by_uuid.get(uuid) { + let security_levels = self.security_levels.read().unwrap(); + if let Some(dev) = security_levels.i_sec_level_by_uuid.get(uuid) { Ok(dev.clone()) } else { Err(Error::sys()).context(err!("KeyMint instance for key not found.")) @@ -142,12 +212,14 @@ impl KeystoreService { _ctx: Option<&CallerInfo>, // reserved for future use sec_level: SecurityLevel, ) -> Result> { - if let Some(dev) = self - .uuid_by_sec_level - .get(&sec_level) - .and_then(|uuid| self.i_sec_level_by_uuid.get(uuid)) - { - Ok(dev.clone()) + let security_levels = self.security_levels.read().unwrap(); + if let Some(uuid) = security_levels.uuid_by_sec_level.get(&sec_level) { + if let Some(dev) = security_levels.i_sec_level_by_uuid.get(uuid) { + Ok(dev.clone()) + } else { + Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + .context(err!("No such security level.")) + } } else { Err(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) .context(err!("No such security level.")) @@ -159,10 +231,11 @@ impl KeystoreService { _ctx: Option<&CallerInfo>, // reserved for future use sec_level: SecurityLevel, ) -> Result> { - if let Some(dev) = self + let security_levels = self.security_levels.read().unwrap(); + if let Some(dev) = security_levels .uuid_by_sec_level .get(&sec_level) - .and_then(|uuid| self.i_osec_level_by_uuid.get(uuid)) + .and_then(|uuid| security_levels.i_osec_level_by_uuid.get(uuid)) { Ok(dev.clone()) } else { @@ -679,4 +752,82 @@ impl IOhMyKsService for KeystoreService { Status::from(rsbinder::StatusCode::UnknownTransaction) }) } + + fn updateEcKeybox( + &self, + key: &[u8], + chain: &[crate::android::hardware::security::keymint::Certificate::Certificate], + ) -> rsbinder::status::Result<()> { + { + let mut keybox = KEYBOX.write().unwrap(); + debug!("Obtained KEYBOX lock to update EC keybox"); + + let chain: Vec = chain + .iter() + .map(|c| kmr_wire::keymint::Certificate { + encoded_certificate: c.encodedCertificate.clone(), + }) + .collect(); + + keybox.update_ec_keybox(key.to_vec(), chain); + } // Release KEYBOX lock before registering security levels + + debug!("Registering security levels after updating EC keybox"); + self.register_security_level(SecurityLevel::TRUSTED_ENVIRONMENT) + .map_err(|e| { + panic!("Failed to register TEE security level: {}", e); + }) + .unwrap(); + + self.register_security_level(SecurityLevel::STRONGBOX) + .map_err(|e| { + log::error!("Failed to register StrongBox security level: {}", e); + log::error!("But we ignore this error because"); + // ignore error, StrongBox is optional + }) + .unwrap_or(()); + + // reset KeyMintTa + get_keymint_wrapper(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap().reset_keymint_ta().unwrap(); + + get_keymint_wrapper(SecurityLevel::STRONGBOX).unwrap().reset_keymint_ta().unwrap(); + + Result::Ok(()) + } + + fn updateRsaKeybox( + &self, + key: &[u8], + chain: &[crate::android::hardware::security::keymint::Certificate::Certificate], + ) -> rsbinder::status::Result<()> { + { + let mut keybox = KEYBOX.write().unwrap(); + + let chain: Vec = chain + .iter() + .map(|c| kmr_wire::keymint::Certificate { + encoded_certificate: c.encodedCertificate.clone(), + }) + .collect(); + + keybox.update_rsa_keybox(key.to_vec(), chain); + } // Release KEYBOX lock before registering security levels + + debug!("Registering security levels after updating RSA keybox"); + self.register_security_level(SecurityLevel::TRUSTED_ENVIRONMENT) + .map_err(|e| { + panic!("Failed to register TEE security level: {}", e); + }) + .unwrap(); + + self.register_security_level(SecurityLevel::STRONGBOX) + .map_err(|e| { + log::error!("Failed to register StrongBox security level: {}", e); + log::error!("But we ignore this error because"); + // ignore error, StrongBox is optional + }) + .unwrap_or(()); + + Result::Ok(()) + } } diff --git a/src/keymaster/utils.rs b/src/keymaster/utils.rs index 90d96d7..e942ab1 100644 --- a/src/keymaster/utils.rs +++ b/src/keymaster/utils.rs @@ -1,15 +1,14 @@ - use crate::{ android::hardware::security::keymint::{ - ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, - KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, - KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, - }, + ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, + KeyCharacteristics::KeyCharacteristics, KeyParameter::KeyParameter as KmKeyParameter, + KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, + }, consts, err, keymaster::{ - error::{map_ks_error, KsError as Error}, + error::{KsError as Error, map_ks_error}, key_parameter::KeyParameter, - keymint_device::{get_keymint_wrapper, KeyMintDevice}, + keymint_device::{KeyMintDevice, get_keymint_wrapper}, }, watchdog, }; diff --git a/src/keymint/mod.rs b/src/keymint/mod.rs index c30cd90..7da306c 100644 --- a/src/keymint/mod.rs +++ b/src/keymint/mod.rs @@ -1,4 +1,3 @@ -pub mod attest; pub mod clock; pub mod rpc; pub mod sdd; diff --git a/src/keymint/sdd.rs b/src/keymint/sdd.rs index e2730e3..b8607ac 100644 --- a/src/keymint/sdd.rs +++ b/src/keymint/sdd.rs @@ -28,10 +28,10 @@ use std::io::Write; use std::path; #[cfg(not(target_os = "android"))] -const SECURE_DELETION_DATA_FILE: &str = "./omk/data/keymint_secure_deletion_data"; +const SECURE_DELETION_DATA_FILE: &str = "./omk/data/keymint.dat"; #[cfg(target_os = "android")] -const SECURE_DELETION_DATA_FILE: &str = "/data/adb/omk/data/keymint_secure_deletion_data"; +const SECURE_DELETION_DATA_FILE: &str = "/data/adb/omk/data/keymint.dat"; fn read_sdd_file() -> Result { let f = fs::File::open(SECURE_DELETION_DATA_FILE).map_err(|e| { diff --git a/src/keymint/soft.rs b/src/keymint/soft.rs index 60ee825..02cc04a 100644 --- a/src/keymint/soft.rs +++ b/src/keymint/soft.rs @@ -22,6 +22,7 @@ use kmr_common::{ }; use kmr_crypto_boring::{hmac::BoringHmac, rng::BoringRng}; use kmr_ta::device::RetrieveKeyMaterial; +use rand::{rngs::StdRng, RngCore, SeedableRng}; /// Root key retrieval using hard-coded fake keys. pub struct Keys; @@ -29,16 +30,26 @@ pub struct Keys; impl RetrieveKeyMaterial for Keys { fn root_kek(&self, _context: &[u8]) -> Result, Error> { // Matches `MASTER_KEY` in system/keymaster/key_blob_utils/software_keyblobs.cpp - Ok(crypto::hmac::Key::new([0; 16].to_vec()).into()) + let mut rng = StdRng::from_seed(b"somedeadbeefcafeherebruhbruhbruh".clone()); + let mut key = [0; 16]; + rng.fill_bytes(&mut key); + + Ok(crypto::hmac::Key::new(key.to_vec()).into()) } fn kak(&self) -> Result, Error> { // Matches `kFakeKeyAgreementKey` in // system/keymaster/km_openssl/soft_keymaster_enforcement.cpp. - Ok(crypto::aes::Key::Aes256([0; 32]).into()) + let mut rng = StdRng::from_seed(b"somedeadbeefcafeherebruhbruhbruh".clone()); + let mut key = [0; 32]; + rng.fill_bytes(&mut key); + + Ok(crypto::aes::Key::Aes256(key).into()) } - fn unique_id_hbk(&self, _ckdf: &dyn crypto::Ckdf) -> Result { - // Matches value used in system/keymaster/contexts/pure_soft_keymaster_context.cpp. - crypto::hmac::Key::new_from(b"Very Very Secret HKDF Key") + fn unique_id_hbk(&self, ckdf: &dyn crypto::Ckdf) -> Result { + // By default, use CKDF on the key agreement secret to derive a key. + let unique_id_label = b"UniqueID HBK 32B"; + ckdf.ckdf(&self.kak()?, unique_id_label, &[], 32) + .map(crypto::hmac::Key::new) } } diff --git a/src/logging.rs b/src/logging.rs index bba5cc3..150e576 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -20,9 +20,19 @@ pub fn init_logger() { #[cfg(target_os = "android")] pub fn init_logger() { - android_logger::init_once( - android_logger::Config::default() - .with_max_level(LevelFilter::Debug) - .with_tag("OhMyKeymint"), - ); + // android_logger::init_once( + // android_logger::Config::default() + // .with_max_level(LevelFilter::Debug) + // .with_tag("OhMyKeymint"), + // ); + + let stdout = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new(PATTERN))) + .build(); + let root = Root::builder().appender("stdout").build(LevelFilter::Debug); + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .build(root) + .unwrap(); + log4rs::init_config(config).unwrap(); } diff --git a/src/main.rs b/src/main.rs index df45ccf..c033269 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,13 +8,17 @@ use log::{debug, error}; use rsbinder::hub; use crate::{ - android::system::keystore2::{ - IKeystoreOperation, IKeystoreService::BnKeystoreService}, keymaster::service::KeystoreService, top::qwq2333::ohmykeymint::IOhMyKsService::BnOhMyKsService} - -; + android::system::keystore2::IKeystoreService::BnKeystoreService, + config::{Backend, CONFIG}, + keymaster::service::KeystoreService, + top::qwq2333::ohmykeymint::IOhMyKsService::BnOhMyKsService, +}; +pub mod att_mgr; +pub mod config; pub mod consts; pub mod global; +pub mod keybox; pub mod keymaster; pub mod keymint; pub mod logging; @@ -33,6 +37,9 @@ const TAG: &str = "OhMyKeymint"; fn main() { logging::init_logger(); debug!("Hello, OhMyKeymint!"); + debug!("Reading config"); + let config = CONFIG.read().unwrap(); + debug!("Initial process state"); rsbinder::ProcessState::init_default(); @@ -44,17 +51,26 @@ fn main() { debug!("Starting thread pool"); rsbinder::ProcessState::start_thread_pool(); - debug!("Creating keystore service"); - let dev = KeystoreService::new_native_binder().unwrap(); - - let service = BnKeystoreService::new_binder(dev.clone()); + match config.main.backend { + Backend::OMK => { + debug!("Using OhMyKeymint backend"); + debug!("Creating keystore service"); + let dev = KeystoreService::new_native_binder().unwrap(); - debug!("Adding keystore service to hub"); - hub::add_service("keystore3", service.as_binder()).unwrap(); + let service = BnKeystoreService::new_binder(dev); + debug!("Adding keystore service to hub"); + hub::add_service("keystore3", service.as_binder()).unwrap(); + } + Backend::TrickyStore => { + debug!("Using TrickyStore backend"); + debug!("Creating keystore service"); + let dev = KeystoreService::new_native_binder().unwrap(); - debug!("Adding OMK service to hub"); - let service = BnOhMyKsService::new_binder(dev); - hub::add_service("omk", service.as_binder()).unwrap(); + debug!("Adding OMK service to hub"); + let service = BnOhMyKsService::new_binder(dev); + hub::add_service("omk", service.as_binder()).unwrap(); + } + } debug!("Joining thread pool"); rsbinder::ProcessState::join_thread_pool().unwrap(); diff --git a/src/plat/aaid.rs b/src/plat/aaid.rs index 91fd274..0237bf6 100644 --- a/src/plat/aaid.rs +++ b/src/plat/aaid.rs @@ -9,5 +9,5 @@ pub struct AttestationApplicationId { #[derive(der::Sequence, der::ValueOrd, Debug)] pub struct PackageInfoRecord { pub package_name: OctetString, - pub version: i64 -} \ No newline at end of file + pub version: i64, +} diff --git a/src/plat/mod.rs b/src/plat/mod.rs index dbf9fe3..b108c22 100644 --- a/src/plat/mod.rs +++ b/src/plat/mod.rs @@ -1,3 +1,3 @@ +pub mod aaid; pub mod property_watcher; pub mod utils; -pub mod aaid; diff --git a/src/plat/utils.rs b/src/plat/utils.rs index 0313d7c..220139a 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -1,13 +1,12 @@ use std::sync::{Arc, Mutex}; use anyhow::Ok; -use der::Encode; use der::asn1::SetOfVec; +use der::Encode; use kmr_common::crypto::Sha256; use kmr_crypto_boring::sha256::BoringSha256; -use log::debug; -use rsbinder::{hub, DeathRecipient, Parcel, Parcelable}; -use serde::de; +use log::{debug, error}; +use rsbinder::{hub, DeathRecipient}; use crate::android::apex::IApexService::IApexService; use crate::android::security::keystore::IKeyAttestationApplicationIdProvider::IKeyAttestationApplicationIdProvider; @@ -51,9 +50,7 @@ fn get_pm() -> anyhow::Result = - hub::get_interface( - "sec_key_att_app_id_provider", - )?; + hub::get_interface("sec_key_att_app_id_provider")?; let recipient = Arc::new(PmDeathRecipient {}); pm.as_binder() @@ -65,6 +62,13 @@ fn get_pm() -> anyhow::Result anyhow::Result> { APEX.with(|p| { @@ -85,6 +89,7 @@ fn get_apex() -> anyhow::Result> { } pub fn get_aaid(uid: u32) -> anyhow::Result> { + debug!("Getting AAID for UID: {}", uid); let application_id = if (uid == 0) || (uid == 1000) { let mut info = KeyAttestationPackageInfo::default(); info.packageName = "AndroidSystem".to_string(); @@ -94,27 +99,40 @@ pub fn get_aaid(uid: u32) -> anyhow::Result> { } } else { let _wd = crate::watchdog::watch("get_aaid: Retrieving AAID by calling service"); - let pm = get_pm()?; - { - let current_uid = unsafe { libc::getuid() }; - let current_euid = unsafe { libc::geteuid() }; - - debug!("Current UID: {}, EUID: {}", current_uid, current_euid); - - unsafe { - libc::seteuid(1017); // KEYSTORE_UID + let mut tried = 0; + loop { + let pm = get_pm()?; + let result = { + let current_uid = unsafe { libc::getuid() }; + let current_euid = unsafe { libc::geteuid() }; + debug!("Current UID: {}, EUID: {}", current_uid, current_euid); + unsafe { + libc::seteuid(1017); // KEYSTORE_UID + } + let result = pm.getKeyAttestationApplicationId(uid as i32); + unsafe { + libc::seteuid(current_euid); + } + result + }; + if let Result::Ok(application_id) = result { + break application_id; + } else { + let e = result.unwrap_err(); + if e.exception_code() == rsbinder::ExceptionCode::TransactionFailed && tried < 2 { + error!("Transaction failed when calling getKeyAttestationApplicationId for UID {}: {:?}", uid, e); + error!("Trying to reset the PM instance to None"); + reset_pm(); + tried += 1; + } else { + return Err(anyhow::anyhow!( + "Failed to get KeyAttestationApplicationId for UID {}, Error: {:?}", + uid, + e + )); + } } - - let result = pm.getKeyAttestationApplicationId(uid as i32) - .map_err(|e| anyhow::anyhow!(err!("getPackagesForUid failed: {:?}", e)))?; - - unsafe { - libc::seteuid(current_euid); - } - - result } - }; debug!("Application ID: {:?}", application_id); @@ -122,26 +140,39 @@ pub fn get_aaid(uid: u32) -> anyhow::Result> { encode_application_id(application_id) } -fn encode_application_id(application_id: KeyAttestationApplicationId) -> Result, anyhow::Error> { +fn encode_application_id( + application_id: KeyAttestationApplicationId, +) -> Result, anyhow::Error> { let mut package_info_set = SetOfVec::new(); let mut signature_digests = SetOfVec::new(); let sha256 = BoringSha256 {}; for pkg in application_id.packageInfos { for sig in pkg.signatures { - let result = sha256.hash(sig.data.as_slice()) + let result = sha256 + .hash(sig.data.as_slice()) .map_err(|e| anyhow::anyhow!("Failed to hash signature: {:?}", e))?; let octet_string = x509_cert::der::asn1::OctetString::new(&result)?; - signature_digests.insert_ordered(octet_string).map_err(|e| anyhow::anyhow!("Failed to encode AttestationApplicationId: {:?}", e))?; + let result = signature_digests.insert_ordered(octet_string).map_err(|e| { + anyhow::anyhow!(err!("Failed to encode AttestationApplicationId: {:?}", e)) + }); + if result.is_err() { + error!( + "Failed to insert signature digest: {:?}", + result.unwrap_err() + ); + } } let package_info = super::aaid::PackageInfoRecord { package_name: der::asn1::OctetString::new(pkg.packageName.as_bytes())?, version: pkg.versionCode, }; - package_info_set.insert_ordered(package_info).map_err(|e| anyhow::anyhow!("Failed to encode AttestationApplicationId: {:?}", e))?; + package_info_set.insert_ordered(package_info).map_err(|e| { + anyhow::anyhow!(err!("Failed to encode AttestationApplicationId: {:?}", e)) + })?; } let result = super::aaid::AttestationApplicationId { @@ -149,8 +180,9 @@ fn encode_application_id(application_id: KeyAttestationApplicationId) -> Result< signature_digests: signature_digests, }; - result.to_der() - .map_err(|e| anyhow::anyhow!("Failed to encode AttestationApplicationId: {:?}", e)) + result + .to_der() + .map_err(|e| anyhow::anyhow!("Failed to encode AttestationApplicationId: {:?}", e)) } pub fn get_apex_module_info() -> anyhow::Result> { diff --git a/ta/src/keys.rs b/ta/src/keys.rs index 5a50bbe..862e622 100644 --- a/ta/src/keys.rs +++ b/ta/src/keys.rs @@ -136,6 +136,12 @@ impl crate::KeyMintTa { // Retrieve the signing key information (which will be dropped when signing is done). let signing_key = sign_info.signing_key(key_type)?; + + log::info!( + "using attestation key with subject {:?}", + String::from_utf8_lossy(&chain_info.issuer) + ); + Ok(SigningInfo { attestation_info: None, signing_key, From 8552bea4b3dc05320a4c462fb6a4660967fc0bb2 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 18 Oct 2025 03:09:39 +0800 Subject: [PATCH 18/46] fix: use seed from config Signed-off-by: qwq233 --- src/keymaster/keymint_device.rs | 8 +++++--- src/keymint/soft.rs | 25 ++++++++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index d8e0fb0..a4eaeb8 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -1151,6 +1151,7 @@ pub fn get_keymaster_security_level( } fn init_keymint_ta(security_level: SecurityLevel) -> Result { + let config = CONFIG.read().unwrap(); let security_level = get_keymint_security_level(security_level)?; let hw_info = HardwareInfo { @@ -1198,7 +1199,10 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { sha256: Box::new(kmr_crypto_boring::sha256::BoringSha256), }; - let keys: Box = Box::new(soft::Keys); + let keys: Box = Box::new(soft::Keys::new( + config.crypto.root_kek_seed.clone(), + config.crypto.kak_seed.clone(), + )); let rpc: Box = Box::new(soft::RpcArtifacts::new( soft::Derive::default(), rpc_sign_algo, @@ -1230,8 +1234,6 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { let mut vb_key = vec![0u8; 32]; rng.fill_bytes(&mut vb_key); - let config = CONFIG.read().unwrap(); - let patch_level = config.trust.security_patch.replace("-", ""); let patch_level = patch_level.parse::().unwrap_or(20250605); let boot_patchlevel = patch_level; diff --git a/src/keymint/soft.rs b/src/keymint/soft.rs index 02cc04a..fa78648 100644 --- a/src/keymint/soft.rs +++ b/src/keymint/soft.rs @@ -25,12 +25,25 @@ use kmr_ta::device::RetrieveKeyMaterial; use rand::{rngs::StdRng, RngCore, SeedableRng}; /// Root key retrieval using hard-coded fake keys. -pub struct Keys; +pub struct Keys { + root_kek_seed: [u8; 32], + kak_seed: [u8; 32], +} + +impl Keys { + /// Creates a new `Keys` instance with the given seeds. + pub fn new(root_kek_seed: [u8; 32], kak_seed: [u8; 32]) -> Self { + Self { + root_kek_seed, + kak_seed, + } + } +} impl RetrieveKeyMaterial for Keys { fn root_kek(&self, _context: &[u8]) -> Result, Error> { // Matches `MASTER_KEY` in system/keymaster/key_blob_utils/software_keyblobs.cpp - let mut rng = StdRng::from_seed(b"somedeadbeefcafeherebruhbruhbruh".clone()); + let mut rng = StdRng::from_seed(self.root_kek_seed); let mut key = [0; 16]; rng.fill_bytes(&mut key); @@ -39,18 +52,12 @@ impl RetrieveKeyMaterial for Keys { fn kak(&self) -> Result, Error> { // Matches `kFakeKeyAgreementKey` in // system/keymaster/km_openssl/soft_keymaster_enforcement.cpp. - let mut rng = StdRng::from_seed(b"somedeadbeefcafeherebruhbruhbruh".clone()); + let mut rng = StdRng::from_seed(self.kak_seed); let mut key = [0; 32]; rng.fill_bytes(&mut key); Ok(crypto::aes::Key::Aes256(key).into()) } - fn unique_id_hbk(&self, ckdf: &dyn crypto::Ckdf) -> Result { - // By default, use CKDF on the key agreement secret to derive a key. - let unique_id_label = b"UniqueID HBK 32B"; - ckdf.ckdf(&self.kak()?, unique_id_label, &[], 32) - .map(crypto::hmac::Key::new) - } } /// Implementation of key derivation using a random fake key. From 5f335909144497a9fe3860e2904afd883ccf3e4f Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 18 Oct 2025 03:19:10 +0800 Subject: [PATCH 19/46] feat: setup terminal and adb logger Signed-off-by: qwq233 --- Cargo.toml | 1 + src/logging.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e73fa78..486eee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ rsproperties-service = "*" [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.15.1" +multi_log = "0.1.2" [profile.release] diff --git a/src/logging.rs b/src/logging.rs index 150e576..212caab 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -20,11 +20,11 @@ pub fn init_logger() { #[cfg(target_os = "android")] pub fn init_logger() { - // android_logger::init_once( - // android_logger::Config::default() - // .with_max_level(LevelFilter::Debug) - // .with_tag("OhMyKeymint"), - // ); + let config = android_logger::Config::default() + .with_max_level(LevelFilter::Debug) + .with_tag("OhMyKeymint"); + + let android_logger = android_logger::AndroidLogger::new(config); let stdout = ConsoleAppender::builder() .encoder(Box::new(PatternEncoder::new(PATTERN))) @@ -34,5 +34,8 @@ pub fn init_logger() { .appender(Appender::builder().build("stdout", Box::new(stdout))) .build(root) .unwrap(); - log4rs::init_config(config).unwrap(); + + let log4rs = log4rs::Logger::new(config); + + multi_log::MultiLogger::init(vec![Box::new(android_logger), Box::new(log4rs)], log::Level::Debug).unwrap(); } From 5a3ba6104c09c0d8dba57d044dadf95b4ce5a705 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 18 Oct 2025 16:00:04 +0800 Subject: [PATCH 20/46] feat: zip it as magisk module Signed-off-by: qwq233 --- Cargo.toml | 9 +- README.md | 5 +- build.py | 231 ++++++++++++++++++ .../META-INF/com/google/android/update-binary | 190 ++++++++++++++ .../com/google/android/updater-script | 1 + template/customize.sh | 84 +++++++ template/daemon | 8 + template/module.prop | 7 + template/post-fs-data.sh | 1 + template/sepolicy.rule | 4 + template/service.sh | 14 ++ template/verify.sh | 51 ++++ 12 files changed, 595 insertions(+), 10 deletions(-) create mode 100644 build.py create mode 100644 template/META-INF/com/google/android/update-binary create mode 100644 template/META-INF/com/google/android/updater-script create mode 100644 template/customize.sh create mode 100644 template/daemon create mode 100644 template/module.prop create mode 100644 template/post-fs-data.sh create mode 100644 template/sepolicy.rule create mode 100644 template/service.sh create mode 100644 template/verify.sh diff --git a/Cargo.toml b/Cargo.toml index 486eee6..30672f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,6 @@ license = "AGPL-3.0-or-later" name = "keymint" path = "src/main.rs" -# [lib] -# name = "ohmykeymint" -# path = "src/lib.rs" -# crate-type = ["cdylib"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] jni = "0.21.1" syscalls = "0.6.15" @@ -92,3 +85,5 @@ rsproperties-service = { path = "rsproperties-service" } [build-dependencies] prost-build = "0.14.1" rsbinder-aidl = "0.4.3" +sha2 = "0.10.9" +zip ="6.0.0" diff --git a/README.md b/README.md index 3f89aa6..b91a710 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Oh My Keymint -Custom keymint implementation for Android Keystore Spoofer +Custom keystore implementation for Android Keystore Spoofer > [!WARNING] > This is a toy project. No ANY guarantees are made regarding performance or stability. @@ -46,7 +46,6 @@ along with this program. If not, see . 5. 本协议与GNU Affero General Public License(以下简称AGPL)共同发挥效力, 当本协议内容与AGPL冲突时,应当优先应用本协议内容,本协议仅覆盖本软件作者拥有 完全著作权的部分,对于使用其他协议的软件代码不发挥效力。 - ``` # Credit @@ -68,4 +67,4 @@ 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. -``` \ No newline at end of file +``` diff --git a/build.py b/build.py new file mode 100644 index 0000000..d3718af --- /dev/null +++ b/build.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +""" +Build script for OhMyKeymint Android targets +""" + +import argparse +import os +import shutil +import subprocess +import hashlib +import zipfile +import toml +import subprocess +import glob + +def get_version_from_cargo_toml(): + """Read version from Cargo.toml""" + with open('./Cargo.toml', 'r') as f: + cargo_toml = toml.load(f) + return cargo_toml['package']['version'] + +def get_git_commit_count(): + """Get git commit count - must be numeric only""" + result = subprocess.run(['git', 'rev-list', '--count', 'HEAD'], + capture_output=True, text=True) + if result.returncode == 0: + git_count = result.stdout.strip() + # Validate that git_count contains only digits + if not git_count.isdigit(): + raise ValueError(f"Git commit count must be numeric only, got: {git_count}") + return git_count + raise Exception("Failed to get git commit count") + +def get_git_commit_hash(): + """Get git commit hash (first 7 characters)""" + result = subprocess.run(['git', 'rev-parse', 'HEAD'], + capture_output=True, text=True) + if result.returncode == 0: + full_hash = result.stdout.strip() + return full_hash[:7] # Return first 7 characters + raise Exception("Failed to get git commit hash") + +def build_target(target, release=False): + """Build for specific target""" + build_type = "release" if release else "debug" + print(f"Building for {target} ({build_type})...") + + cmd = ['cargo', 'ndk', '-t', target, '-o', 'build', 'build'] + if release: + cmd.append('--release') + + result = subprocess.run(cmd) + if result.returncode != 0: + raise Exception(f"Build failed for {target}") + +def copy_binary(target, arch_name, release=False): + """Copy built binary from correct target directory""" + build_type = "release" if release else "debug" + source_path = f"target/{target}/{build_type}/keymint" + + if not os.path.exists(source_path): + raise FileNotFoundError(f"Binary not found at {source_path}") + + dest_dir = f"target/temp/libs/{arch_name}" + os.makedirs(dest_dir, exist_ok=True) + shutil.copy2(source_path, f"{dest_dir}/keymint") + print(f"Copied binary from {source_path} to {dest_dir}/keymint") + +def copy_template_files(): + """Copy template directory contents to temp folder""" + temp_dir = "target/temp" + template_dir = "template" + + if not os.path.exists(template_dir): + raise FileNotFoundError("Template directory not found") + + print("Copying template files...") + for item in os.listdir(template_dir): + src = os.path.join(template_dir, item) + dst = os.path.join(temp_dir, item) + if os.path.isdir(src): + shutil.copytree(src, dst, dirs_exist_ok=True) + else: + shutil.copy2(src, dst) + +def modify_module_prop(version, git_count, git_hash): + """Modify module.prop file with version, git count and git hash""" + module_prop_path = "target/temp/module.prop" + + if not os.path.exists(module_prop_path): + raise FileNotFoundError(f"module.prop not found at {module_prop_path}") + + print("Modifying module.prop file...") + + with open(module_prop_path, 'r') as f: + content = f.read() + + # Create version name with git hash + version_name = f"{version}-{git_hash}" + + # Replace placeholders + content = content.replace("${versionName}", version_name) + content = content.replace("${versionCode}", git_count) + + with open(module_prop_path, 'w') as f: + f.write(content) + + print(f"Updated module.prop: versionName={version_name}, versionCode={git_count}") + +def generate_hash_for_file(file_path): + """Generate SHA256 hash for a single file""" + with open(file_path, 'rb') as f: + file_hash = hashlib.sha256(f.read()).hexdigest() + + hash_file = f"{file_path}.sha256" + with open(hash_file, 'w') as f: + f.write(file_hash) + print(f"Created hash file: {hash_file}") + +def generate_hash_files(): + """Generate SHA256 hash files for root directory files and binaries""" + temp_dir = "target/temp" + + print("Generating SHA256 hash files...") + + # Generate hash files for root directory files + for item in os.listdir(temp_dir): + item_path = os.path.join(temp_dir, item) + if os.path.isfile(item_path): + generate_hash_for_file(item_path) + + # Generate hash files for binaries in libs directory + libs_dir = os.path.join(temp_dir, "libs") + if os.path.exists(libs_dir): + for arch_dir in os.listdir(libs_dir): + arch_path = os.path.join(libs_dir, arch_dir) + if os.path.isdir(arch_path): + for binary in os.listdir(arch_path): + binary_path = os.path.join(arch_path, binary) + if os.path.isfile(binary_path): + generate_hash_for_file(binary_path) + +def delete_old_zips(release=False): + """Delete all old zip files with the same build type""" + build_type = "release" if release else "debug" + pattern = f"target/OhMyKeymint-{build_type}-*.zip" + + old_zips = glob.glob(pattern) + if old_zips: + print(f"Found {len(old_zips)} old zip file(s) to delete:") + for old_zip in old_zips: + print(f" Deleting: {old_zip}") + os.remove(old_zip) + else: + print(f"No old zip files found matching pattern: {pattern}") + +def create_zip_package(version, git_hash, release=False): + """Create final zip package""" + build_type = "release" if release else "debug" + zip_name = f"target/OhMyKeymint-{build_type}-{version}-{git_hash}.zip" + + print(f"Creating zip package: {zip_name}") + with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk("target/temp"): + for file in files: + file_path = os.path.join(root, file) + arcname = os.path.relpath(file_path, "target/temp") + zipf.write(file_path, arcname) + + return zip_name + +def main(): + parser = argparse.ArgumentParser(description='Build OhMyKeymint for Android') + parser.add_argument('--release', action='store_true', + help='Build in release mode') + args = parser.parse_args() + + # Clean up previous temp directory + if os.path.exists("target/temp"): + shutil.rmtree("target/temp") + + # Create target directory structure + os.makedirs("target/temp", exist_ok=True) + + try: + # Get version and git info + version = get_version_from_cargo_toml() + git_count = get_git_commit_count() + git_hash = get_git_commit_hash() + + print(f"Building OhMyKeymint version {version} (commit {git_count}, hash {git_hash})") + print(f"Build mode: {'Release' if args.release else 'Debug'}") + + # Delete all old zip files with the same build type + delete_old_zips(args.release) + + # Build targets + targets = [ + ("aarch64-linux-android", "arm64-v8a"), + # ("x86_64-linux-android", "x86_64") + ] + + for target, arch_name in targets: + build_target(target, args.release) + copy_binary(target, arch_name, args.release) + + # Copy template files + copy_template_files() + + # Modify module.prop file + modify_module_prop(version, git_count, git_hash) + + # Generate hash files for all files including binaries + generate_hash_files() + + # Create final zip + zip_path = create_zip_package(version, git_hash, args.release) + + print(f"Build completed successfully!") + print(f"Output: {zip_path}") + + except Exception as e: + print(f"Build failed: {e}") + # Clean up on failure + if os.path.exists("target/temp"): + shutil.rmtree("target/temp") + exit(1) + +if __name__ == "__main__": + main() + diff --git a/template/META-INF/com/google/android/update-binary b/template/META-INF/com/google/android/update-binary new file mode 100644 index 0000000..a4058e6 --- /dev/null +++ b/template/META-INF/com/google/android/update-binary @@ -0,0 +1,190 @@ +#!/sbin/sh + +################# +# Initialization +################# + +umask 022 + +# echo before loading util_functions +ui_print() { echo "$1"; } + +require_new_magisk() { + ui_print "*******************************" + ui_print " Please install Magisk v19.0+! " + ui_print "*******************************" + exit 1 +} + +######################### +# Load util_functions.sh +######################### + +OUTFD=$2 +ZIPFILE=$3 + +mount /data 2>/dev/null + +[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk +. /data/adb/magisk/util_functions.sh +[ $MAGISK_VER_CODE -lt 19000 ] && require_new_magisk + +if [ $MAGISK_VER_CODE -ge 20400 ]; then + # New Magisk have complete installation logic within util_functions.sh + install_module + exit 0 +fi + +################# +# Legacy Support +################# + +TMPDIR=/dev/tmp +PERSISTDIR=/sbin/.magisk/mirror/persist + +is_legacy_script() { + unzip -l "$ZIPFILE" install.sh | grep -q install.sh + return $? +} + +print_modname() { + local len + len=`echo -n $MODNAME | wc -c` + len=$((len + 2)) + local pounds=`printf "%${len}s" | tr ' ' '*'` + ui_print "$pounds" + ui_print " $MODNAME " + ui_print "$pounds" + ui_print "*******************" + ui_print " Powered by Magisk " + ui_print "*******************" +} + +# Override abort as old scripts have some issues +abort() { + ui_print "$1" + $BOOTMODE || recovery_cleanup + [ -n $MODPATH ] && rm -rf $MODPATH + rm -rf $TMPDIR + exit 1 +} + +rm -rf $TMPDIR 2>/dev/null +mkdir -p $TMPDIR + +# Preperation for flashable zips +setup_flashable + +# Mount partitions +mount_partitions + +# Detect version and architecture +api_level_arch_detect + +# Setup busybox and binaries +$BOOTMODE && boot_actions || recovery_actions + +############## +# Preparation +############## + +# Extract prop file +unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2 +[ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!" + +$BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules +MODULEROOT=$NVBASE/$MODDIRNAME +MODID=`grep_prop id $TMPDIR/module.prop` +MODPATH=$MODULEROOT/$MODID +MODNAME=`grep_prop name $TMPDIR/module.prop` + +# Create mod paths +rm -rf $MODPATH 2>/dev/null +mkdir -p $MODPATH + +########## +# Install +########## + +if is_legacy_script; then + unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2 + + # Load install script + . $TMPDIR/install.sh + + # Callbacks + print_modname + on_install + + # Custom uninstaller + [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh + + # Skip mount + $SKIPMOUNT && touch $MODPATH/skip_mount + + # prop file + $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop + + # Module info + cp -af $TMPDIR/module.prop $MODPATH/module.prop + + # post-fs-data scripts + $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh + + # service scripts + $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh + + ui_print "- Setting permissions" + set_permissions +else + print_modname + + unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2 + + if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then + ui_print "- Extracting module files" + unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2 + + # Default permissions + set_perm_recursive $MODPATH 0 0 0755 0644 + fi + + # Load customization script + [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh +fi + +# Handle replace folders +for TARGET in $REPLACE; do + ui_print "- Replace target: $TARGET" + mktouch $MODPATH$TARGET/.replace +done + +if $BOOTMODE; then + # Update info for Magisk Manager + mktouch $NVBASE/modules/$MODID/update + cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop +fi + +# Copy over custom sepolicy rules +if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then + ui_print "- Installing custom sepolicy patch" + PERSISTMOD=$PERSISTDIR/magisk/$MODID + mkdir -p $PERSISTMOD + cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule +fi + +# Remove stuffs that don't belong to modules +rm -rf \ +$MODPATH/system/placeholder $MODPATH/customize.sh \ +$MODPATH/README.md $MODPATH/.git* 2>/dev/null + +############# +# Finalizing +############# + +cd / +$BOOTMODE || recovery_cleanup +rm -rf $TMPDIR + +ui_print "- Done" +exit 0 \ No newline at end of file diff --git a/template/META-INF/com/google/android/updater-script b/template/META-INF/com/google/android/updater-script new file mode 100644 index 0000000..11d5c96 --- /dev/null +++ b/template/META-INF/com/google/android/updater-script @@ -0,0 +1 @@ +#MAGISK diff --git a/template/customize.sh b/template/customize.sh new file mode 100644 index 0000000..e789685 --- /dev/null +++ b/template/customize.sh @@ -0,0 +1,84 @@ +# shellcheck disable=SC2034 +SKIPUNZIP=1 + +DEBUG=@DEBUG@ +SONAME=@SONAME@ +SUPPORTED_ABIS="@SUPPORTED_ABIS@" +MIN_SDK=@MIN_SDK@ + +if [ "$BOOTMODE" ] && [ "$KSU" ]; then + ui_print "- Installing from KernelSU app" + ui_print "- KernelSU version: $KSU_KERNEL_VER_CODE (kernel) + $KSU_VER_CODE (ksud)" + if [ "$(which magisk)" ]; then + ui_print "*********************************************************" + ui_print "! Multiple root implementation is NOT supported!" + ui_print "! Please uninstall Magisk before installing Oh My Keymint" + abort "*********************************************************" + fi +elif [ "$BOOTMODE" ] && [ "$MAGISK_VER_CODE" ]; then + ui_print "- Installing from Magisk app" +else + ui_print "*********************************************************" + ui_print "! Install from recovery is not supported" + ui_print "! Please install from KernelSU or Magisk app" + abort "*********************************************************" +fi + +VERSION=$(grep_prop version "${TMPDIR}/module.prop") +ui_print "- Installing $SONAME $VERSION" + +# check architecture +support=false +for abi in $SUPPORTED_ABIS +do + if [ "$ARCH" == "$abi" ]; then + support=true + fi +done +if [ "$support" == "false" ]; then + abort "! Unsupported platform: $ARCH" +else + ui_print "- Device platform: $ARCH" +fi + +# check android +if [ "$API" -lt $MIN_SDK ]; then + ui_print "! Unsupported sdk: $API" + abort "! Minimal supported sdk is $MIN_SDK" +else + ui_print "- Device sdk: $API" +fi + +ui_print "- Extracting verify.sh" +unzip -o "$ZIPFILE" 'verify.sh' -d "$TMPDIR" >&2 +if [ ! -f "$TMPDIR/verify.sh" ]; then + ui_print "*********************************************************" + ui_print "! Unable to extract verify.sh!" + ui_print "! This zip may be corrupted, please try downloading again" + abort "*********************************************************" +fi +. "$TMPDIR/verify.sh" +extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip" +extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip" + +ui_print "- Extracting module files" +extract "$ZIPFILE" 'module.prop' "$MODPATH" +extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" +extract "$ZIPFILE" 'service.sh' "$MODPATH" +extract "$ZIPFILE" 'sepolicy.rule' "$MODPATH" +extract "$ZIPFILE" 'daemon' "$MODPATH" +chmod 755 "$MODPATH/daemon" + + +if [ "$ARCH" = "x64" ]; then + ui_print "- Extracting x64 libraries" + extract "$ZIPFILE" "lib/x86_64/keymint" "$MODPATH" true +else + ui_print "- Extracting arm64 libraries" + extract "$ZIPFILE" "lib/arm64-v8a/keymint" "$MODPATH" true +fi + +chmod 755 "$MODPATH/keymint" + +CONFIG_DIR=/data/adb/oh_my_keymint + diff --git a/template/daemon b/template/daemon new file mode 100644 index 0000000..a09d291 --- /dev/null +++ b/template/daemon @@ -0,0 +1,8 @@ +#!/system/bin/sh + +if [ -f "/data/adb/omk/keymint" ]; then + chmod 0755 "/data/adb/omk/keymint" + exec "/data/adb/omk/keymint" "$@" +else + exec "./keymint" "$@" +fi diff --git a/template/module.prop b/template/module.prop new file mode 100644 index 0000000..f903457 --- /dev/null +++ b/template/module.prop @@ -0,0 +1,7 @@ +id=oh_my_keymint +name=Oh My Keymint +version=${versionName} +versionCode=${versionCode} +author=James Clef +description=Custom keystore implementation for Android Keystore Spoofer +updateJson=https://raw.githubusercontent.com/qwq233/OhMyKeymint/changelog/update.json diff --git a/template/post-fs-data.sh b/template/post-fs-data.sh new file mode 100644 index 0000000..08eb364 --- /dev/null +++ b/template/post-fs-data.sh @@ -0,0 +1 @@ +MODDIR=${0%/*} diff --git a/template/sepolicy.rule b/template/sepolicy.rule new file mode 100644 index 0000000..c1d02a4 --- /dev/null +++ b/template/sepolicy.rule @@ -0,0 +1,4 @@ +allow keystore system_file unix_dgram_socket * +allow system_file keystore unix_dgram_socket * +allow keystore system_file file * +allow crash_dump keystore process * diff --git a/template/service.sh b/template/service.sh new file mode 100644 index 0000000..9acbb09 --- /dev/null +++ b/template/service.sh @@ -0,0 +1,14 @@ +DEBUG=@DEBUG@ + +MODDIR=${0%/*} + +cd $MODDIR + +( +while [ true ]; do + ./daemon + if [ $? -ne 0 ]; then + exit 1 + fi +done +) & diff --git a/template/verify.sh b/template/verify.sh new file mode 100644 index 0000000..45e0521 --- /dev/null +++ b/template/verify.sh @@ -0,0 +1,51 @@ +TMPDIR_FOR_VERIFY="$TMPDIR/.vunzip" +mkdir "$TMPDIR_FOR_VERIFY" + +abort_verify() { + ui_print "*********************************************************" + ui_print "! $1" + ui_print "! This zip may be corrupted, please try downloading again" + abort "*********************************************************" +} + +# extract +extract() { + zip=$1 + file=$2 + dir=$3 + junk_paths=$4 + [ -z "$junk_paths" ] && junk_paths=false + opts="-o" + [ $junk_paths = true ] && opts="-oj" + + file_path="" + hash_path="" + if [ $junk_paths = true ]; then + file_path="$dir/$(basename "$file")" + hash_path="$TMPDIR_FOR_VERIFY/$(basename "$file").sha256" + else + file_path="$dir/$file" + hash_path="$TMPDIR_FOR_VERIFY/$file.sha256" + fi + + unzip $opts "$zip" "$file" -d "$dir" >&2 + [ -f "$file_path" ] || abort_verify "$file not exists" + + unzip $opts "$zip" "$file.sha256" -d "$TMPDIR_FOR_VERIFY" >&2 + [ -f "$hash_path" ] || abort_verify "$file.sha256 not exists" + + (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" + ui_print "- Verified $file" >&1 +} + +file="META-INF/com/google/android/update-binary" +file_path="$TMPDIR_FOR_VERIFY/$file" +hash_path="$file_path.sha256" +unzip -o "$ZIPFILE" "META-INF/com/google/android/*" -d "$TMPDIR_FOR_VERIFY" >&2 +[ -f "$file_path" ] || abort_verify "$file not exists" +if [ -f "$hash_path" ]; then + (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" + ui_print "- Verified $file" >&1 +else + ui_print "- Download from Magisk app" +fi From 1cae6d1ea9c1366538946ef5620c4dba6790271c Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 18 Oct 2025 16:03:57 +0800 Subject: [PATCH 21/46] feat: we only support arm64-v8a for now Signed-off-by: qwq233 --- Cargo.toml | 2 -- template/customize.sh | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 30672f9..c2e433d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,5 +85,3 @@ rsproperties-service = { path = "rsproperties-service" } [build-dependencies] prost-build = "0.14.1" rsbinder-aidl = "0.4.3" -sha2 = "0.10.9" -zip ="6.0.0" diff --git a/template/customize.sh b/template/customize.sh index e789685..684cca5 100644 --- a/template/customize.sh +++ b/template/customize.sh @@ -70,13 +70,13 @@ extract "$ZIPFILE" 'daemon' "$MODPATH" chmod 755 "$MODPATH/daemon" -if [ "$ARCH" = "x64" ]; then - ui_print "- Extracting x64 libraries" - extract "$ZIPFILE" "lib/x86_64/keymint" "$MODPATH" true -else +# if [ "$ARCH" = "x64" ]; then +# ui_print "- Extracting x64 libraries" +# extract "$ZIPFILE" "lib/x86_64/keymint" "$MODPATH" true +# else ui_print "- Extracting arm64 libraries" extract "$ZIPFILE" "lib/arm64-v8a/keymint" "$MODPATH" true -fi +# fi chmod 755 "$MODPATH/keymint" From 9897e12c8f30b09fafa979f23e2693dc32e3f2cf Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 18 Oct 2025 16:37:19 +0800 Subject: [PATCH 22/46] feat: add ci Signed-off-by: qwq233 --- .github/workflows/ci.yml | 152 +++++++++++++++++++++++++ .github/workflows/update-changelog.yml | 76 +++++++++++++ build.py | 2 + 3 files changed, 230 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/update-changelog.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0988b63 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,152 @@ +name: Build OhMyKeymint + +on: + push: + tags: + - 'v*' + pull_request: + workflow_dispatch: + +env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: short + +jobs: + build-boringssl: + runs-on: ubuntu-latest + steps: + - name: Clone BoringSSL + run: | + git clone https://boringssl.googlesource.com/boringssl + + - name: Cache BoringSSL build + uses: actions/cache@v4 + id: boringssl-cache + with: + path: boringssl/build + key: boringssl-${{ hashFiles('boringssl/.git/HEAD', 'boringssl/.git/refs/heads/main') }} + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + target: aarch64-linux-android + + - name: Install Android SDK + if: steps.boringssl-cache.outputs.cache-hit != 'true' + uses: android-actions/setup-android@v3 + - name: Install Android NDK and bindgen + if: steps.boringssl-cache.outputs.cache-hit != 'true' + run: | + cargo install bindgen-cli + echo "y" | sdkmanager --install "ndk;28.1.13356709" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null + echo "y" | sdkmanager --install "ndk-bundle" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null + echo "y" | sdkmanager --install "cmake;4.1.2" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null + + - name: Install build dependencies + if: steps.boringssl-cache.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build gcc-multilib + + - name: Build BoringSSL + if: steps.boringssl-cache.outputs.cache-hit != 'true' + run: | + cd boringssl + export PATH="~/.cargo/bin:$PATH" + $ANDROID_SDK_ROOT/cmake/4.1.2/bin/cmake -GNinja -B build \ + -DRUST_BINDINGS=aarch64-linux-android \ + -DCMAKE_SYSROOT=$ANDROID_SDK_ROOT/ndk/28.1.13356709/toolchains/llvm/prebuilt/linux-x86_64/sysroot/ \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_SDK_ROOT/ndk/28.1.13356709/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=arm64-v8a \ + -DANDROID_PLATFORM=android-21 \ + -DANDROID_STL=c++_shared + ninja -j$(nproc) -C build + - name: Tar BoringSSL + run: | + tar czf boringssl.tar.gz boringssl + + - name: Output BoringSSL + uses: actions/upload-artifact@v4 + with: + name: boringssl.tar.gz + path: boringssl.tar.gz + + setup-and-build: + runs-on: ubuntu-latest + needs: + - build-boringssl + strategy: + matrix: + build-type: [debug, release] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + target: aarch64-linux-android + + - name: Cache Cargo registry and build artifacts + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Install Android SDK and NDK + uses: android-actions/setup-android@v3 + - name: Install Android NDK + run: | + echo "y" | sdkmanager --install "ndk;28.1.13356709" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null + echo "y" | sdkmanager --install "ndk-bundle" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null + cargo install cargo-ndk bindgen-cli + + - name: Download BoringSSL + uses: actions/download-artifact@v4 + with: + name: boringssl.tar.gz + path: . + + - name: Adding BoringSSL to cargo crate + run: | + tar xf boringssl.tar.gz + echo -e "\n[patch.crates-io]\nbssl-sys = { path = \"$(pwd)/boringssl/rust/bssl-sys\" }\n" >> ~/.cargo/config.toml + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install toml + + - name: Build OhMyKeymint + id: prepareArtifact + run: | + python build.py --${{ matrix.build-type }} + name=`ls target/OhMyKeymint-*.zip | awk -F '(/|.zip)' '{print $3}'` && echo "${{ matrix.build-type }}Name=$name" >> $GITHUB_OUTPUT + env: + BORINGSSL_ROOT_DIR: ${{ github.workspace }}/boringssl + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + id: upload + with: + name: ohmykeymint-${{ matrix.build-type }} + path: target/OhMyKeymint-${{ matrix.build-type }}-*.zip + retention-days: 30 + + - name: Attest release build provenance + if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.build-type == 'release' }} + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ steps.prepareArtifact.outputs.releaseName }} + subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }} diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml new file mode 100644 index 0000000..d33a383 --- /dev/null +++ b/.github/workflows/update-changelog.yml @@ -0,0 +1,76 @@ +name: Update Changelog and Metadata + +on: + release: + types: ["published", "edited"] + +jobs: + update-changelog: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository (master) + uses: actions/checkout@v4 + with: + ref: master + fetch-depth: 0 + + - name: Get commit count (versionCode) + id: commit_count + run: | + COUNT=$(git rev-list --count HEAD) + echo "versionCode=$COUNT" >> $GITHUB_OUTPUT + + - name: Extract release info + id: release_info + env: + ASSETS_JSON: ${{ toJson(github.event.release.assets) }} + run: | + TAG_NAME="${{ github.event.release.tag_name }}" + RELEASE_BODY="${{ github.event.release.body }}" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + echo "release_body<> $GITHUB_OUTPUT + echo "$RELEASE_BODY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + ASSET_URL=$(echo "$ASSETS_JSON" | jq -r '.[] | select(.name | endswith("release.zip")) | .browser_download_url' | head -n1) + + if [ -z "$ASSET_URL" ]; then + echo "::error::No release zip found in assets! Names:" + echo "$ASSETS_JSON" | jq -r '.[].name' + exit 1 + fi + echo "asset_url=$ASSET_URL" >> $GITHUB_OUTPUT + + - name: Checkout changelog branch + uses: actions/checkout@v4 + with: + ref: changelog + path: changelog-branch + + - name: Create/update files + run: | + cd changelog-branch + + echo "${{ steps.release_info.outputs.release_body }}" > changelog.md + + cat < update.json + { + "version": "${{ steps.release_info.outputs.tag_name }}", + "versionCode": ${{ steps.commit_count.outputs.versionCode }}, + "zipUrl": "${{ steps.release_info.outputs.asset_url }}", + "changelog": "https://raw.githubusercontent.com/qwq233/OhMyKeymint/changelog/changelog.md" + } + EOF + + - name: Commit and push changes + run: | + cd changelog-branch + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add changelog.md update.json + git commit -m "Update ${{ steps.release_info.outputs.tag_name }}" + git push origin changelog -f diff --git a/build.py b/build.py index d3718af..46d35ca 100644 --- a/build.py +++ b/build.py @@ -173,6 +173,8 @@ def main(): parser = argparse.ArgumentParser(description='Build OhMyKeymint for Android') parser.add_argument('--release', action='store_true', help='Build in release mode') + parser.add_argument('--debug', action='store_true', + help='Build in debug mode (default)') args = parser.parse_args() # Clean up previous temp directory From 6a6dd96f9c7bb2ad83d00ea6b57b83a3aed4d108 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 18 Oct 2025 18:33:05 +0800 Subject: [PATCH 23/46] fix: ci Signed-off-by: qwq233 --- .github/workflows/ci.yml | 40 +++++++++++++++++++--------------- build.rs | 2 ++ common/src/crypto/traits.rs | 1 - src/keymaster/key_parameter.rs | 2 +- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0988b63..fa9d6cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,13 +4,16 @@ on: push: tags: - 'v*' + branches: [ "master" ] + paths-ignore: + - '**.md' + - '**.txt' + - '.github/**' + - '!.github/workflows/**' pull_request: + branches: [ "master" ] workflow_dispatch: -env: - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: short - jobs: build-boringssl: runs-on: ubuntu-latest @@ -39,7 +42,7 @@ jobs: if: steps.boringssl-cache.outputs.cache-hit != 'true' run: | cargo install bindgen-cli - echo "y" | sdkmanager --install "ndk;28.1.13356709" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null + echo "y" | sdkmanager --install "ndk;28.2.13676358" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null echo "y" | sdkmanager --install "ndk-bundle" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null echo "y" | sdkmanager --install "cmake;4.1.2" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null @@ -56,8 +59,8 @@ jobs: export PATH="~/.cargo/bin:$PATH" $ANDROID_SDK_ROOT/cmake/4.1.2/bin/cmake -GNinja -B build \ -DRUST_BINDINGS=aarch64-linux-android \ - -DCMAKE_SYSROOT=$ANDROID_SDK_ROOT/ndk/28.1.13356709/toolchains/llvm/prebuilt/linux-x86_64/sysroot/ \ - -DCMAKE_TOOLCHAIN_FILE=$ANDROID_SDK_ROOT/ndk/28.1.13356709/build/cmake/android.toolchain.cmake \ + -DCMAKE_SYSROOT=$ANDROID_SDK_ROOT/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/sysroot/ \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_SDK_ROOT/ndk/28.2.13676358/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-21 \ -DANDROID_STL=c++_shared @@ -71,6 +74,7 @@ jobs: with: name: boringssl.tar.gz path: boringssl.tar.gz + retention-days: 3 setup-and-build: runs-on: ubuntu-latest @@ -104,13 +108,16 @@ jobs: restore-keys: | ${{ runner.os }}-cargo- - - name: Install Android SDK and NDK + - name: Install Android SDK uses: android-actions/setup-android@v3 - name: Install Android NDK run: | - echo "y" | sdkmanager --install "ndk;28.1.13356709" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null - echo "y" | sdkmanager --install "ndk-bundle" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null - cargo install cargo-ndk bindgen-cli + echo "y" | sdkmanager --install "ndk;28.2.13676358" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null + cargo install cargo-ndk + - name: Install protoc + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler - name: Download BoringSSL uses: actions/download-artifact@v4 @@ -131,18 +138,17 @@ jobs: - name: Build OhMyKeymint id: prepareArtifact run: | - python build.py --${{ matrix.build-type }} - name=`ls target/OhMyKeymint-*.zip | awk -F '(/|.zip)' '{print $3}'` && echo "${{ matrix.build-type }}Name=$name" >> $GITHUB_OUTPUT - env: - BORINGSSL_ROOT_DIR: ${{ github.workspace }}/boringssl + unset RUSTFLAGS + RUSTFLAGS="-C link-arg=-lc++_shared -C link-arg=-lc++abi" python build.py --${{ matrix.build-type }} + name=`ls target/OhMyKeymint-* | awk -F '/' '{print $2}'` && echo "releaseName=$name" >> $GITHUB_OUTPUT - name: Upload build artifacts uses: actions/upload-artifact@v4 id: upload with: - name: ohmykeymint-${{ matrix.build-type }} + name: ${{ steps.prepareArtifact.outputs.releaseName }} path: target/OhMyKeymint-${{ matrix.build-type }}-*.zip - retention-days: 30 + retention-days: 90 - name: Attest release build provenance if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.build-type == 'release' }} diff --git a/build.rs b/build.rs index 8f5448a..30e9570 100644 --- a/build.rs +++ b/build.rs @@ -60,10 +60,12 @@ fn main() { let generated_path = PathBuf::from(format!("{}/aidl.rs", std::env::var("OUT_DIR").unwrap())); let content = fs::read_to_string(&generated_path).unwrap(); + // dirty fixes for name conflicts and incorrect types let patched_content = content .replace("SecurityLevel.", "super::super::super::hardware::security::keymint::SecurityLevel::SecurityLevel::") .replace("HardwareAuthenticatorType.", "super::super::super::hardware::security::keymint::HardwareAuthenticatorType::HardwareAuthenticatorType::") .replace("r#authenticatorType: super::Digest::Digest::NONE,", "r#authenticatorType: super::HardwareAuthenticatorType::HardwareAuthenticatorType::NONE,") + .replace("r#authenticatorType: super::KeyPermission::KeyPermission::NONE,", "r#authenticatorType: super::HardwareAuthenticatorType::HardwareAuthenticatorType::NONE,") .replace("r#operation: rsbinder::Strong,", "r#operation: Option>,"); println!("Patched AIDL content:\n{}", generated_path.display()); diff --git a/common/src/crypto/traits.rs b/common/src/crypto/traits.rs index b88789b..c5c8f8f 100644 --- a/common/src/crypto/traits.rs +++ b/common/src/crypto/traits.rs @@ -18,7 +18,6 @@ use crate::{crypto::ec::Key, der_err, explicit, keyblob, vec_try, Error}; use der::Decode; use kmr_wire::{keymint, keymint::Digest, KeySizeInBits, RsaExponent}; use log::{error, warn}; -use rand::RngCore as _; /// Combined collection of trait implementations that must be provided. pub struct Implementation { diff --git a/src/keymaster/key_parameter.rs b/src/keymaster/key_parameter.rs index 3cef590..b3399b1 100644 --- a/src/keymaster/key_parameter.rs +++ b/src/keymaster/key_parameter.rs @@ -430,7 +430,7 @@ macro_rules! implement_to_sql { }; (@replace_type_spec $enum_name:ident, [$($out:tt)*], []) => { /// Converts $enum_name to be stored in a rusqlite database. - fn to_sql(&self) -> SqlResult { + fn to_sql(&self) -> SqlResult> { match self { $($out)* } From 0e7e0e92a71d975e285e7c165feea177b41e7bb0 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sun, 19 Oct 2025 05:07:55 +0800 Subject: [PATCH 24/46] feat: improve ci --- .github/workflows/ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa9d6cf..f880c5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: key: boringssl-${{ hashFiles('boringssl/.git/HEAD', 'boringssl/.git/refs/heads/main') }} - name: Install Rust toolchain + if: steps.boringssl-cache.outputs.cache-hit != 'true' uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: nightly @@ -67,7 +68,7 @@ jobs: ninja -j$(nproc) -C build - name: Tar BoringSSL run: | - tar czf boringssl.tar.gz boringssl + tar czf boringssl.tar.gz boringssl/build boringssl/rust - name: Output BoringSSL uses: actions/upload-artifact@v4 @@ -103,8 +104,7 @@ jobs: path: | ~/.cargo/registry ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-cargo- @@ -140,14 +140,15 @@ jobs: run: | unset RUSTFLAGS RUSTFLAGS="-C link-arg=-lc++_shared -C link-arg=-lc++abi" python build.py --${{ matrix.build-type }} - name=`ls target/OhMyKeymint-* | awk -F '/' '{print $2}'` && echo "releaseName=$name" >> $GITHUB_OUTPUT + name=`ls target/OhMyKeymint-* | awk -F '(/|.zip)' '{print $2}'` && echo "releaseName=$name" >> $GITHUB_OUTPUT + unzip target/OhMyKeymint-*.zip -d module-${{ matrix.build-type }} - name: Upload build artifacts uses: actions/upload-artifact@v4 id: upload with: name: ${{ steps.prepareArtifact.outputs.releaseName }} - path: target/OhMyKeymint-${{ matrix.build-type }}-*.zip + path: "./module-${{ matrix.build-type }}/*" retention-days: 90 - name: Attest release build provenance From b034d909b82be802bf2ee2b69ee0263e8884f2a0 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sun, 19 Oct 2025 05:30:47 +0800 Subject: [PATCH 25/46] feat: setup dependabot Signed-off-by: qwq233 --- .github/dependabot.yml | 6 ++++++ Cargo.toml | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..219c13a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "daily" diff --git a/Cargo.toml b/Cargo.toml index c2e433d..481a807 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,3 @@ -cargo-features = ["profile-rustflags", "trim-paths"] - [package] name = "OhMyKeymint" version = "0.1.1" @@ -58,7 +56,6 @@ codegen-units = 1 # Reduce number of codegen units to increase optimizations panic = 'abort' # Abort on panic strip = true # Strip symbols from binary* debuginfo = 0 # Disable debug info -trim-paths = "all" [workspace] members = [ From 6cc490f3d15560aa6fc981280117120515c3252c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Oct 2025 16:36:33 +0800 Subject: [PATCH 26/46] chore(deps): update coset requirement from 0.3.3 to 0.4.0 (#3) Updates the requirements on [coset](https://github.com/google/coset) to permit the latest version. - [Changelog](https://github.com/google/coset/blob/main/CHANGELOG.md) - [Commits](https://github.com/google/coset/compare/v0.3.3...v0.4.0) --- updated-dependencies: - dependency-name: coset dependency-version: 0.4.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- common/Cargo.toml | 2 +- ta/Cargo.toml | 2 +- wire/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index 822b523..e4c3d9f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -12,7 +12,7 @@ license = "Apache-2.0" cddl-cat = { version = "^0.6.1", optional = true } ciborium = { version = "^0.2.0", default-features = false } ciborium-io = "^0.2.0" -coset = "0.3.3" +coset = "0.4.0" der = { version = "^0.7.2", features = ["alloc", "derive"] } enumn = "0.1.4" kmr-derive = "*" diff --git a/ta/Cargo.toml b/ta/Cargo.toml index a05ccf9..e1ccb10 100644 --- a/ta/Cargo.toml +++ b/ta/Cargo.toml @@ -17,7 +17,7 @@ downgrade = [] [dependencies] ciborium = { version = "^0.2.0", default-features = false } ciborium-io = "^0.2.0" -coset = "0.3.3" +coset = "0.4.0" der = { version = "^0.7.8", features = ["alloc", "derive"] } flagset = "0.4.3" kmr-common = "*" diff --git a/wire/Cargo.toml b/wire/Cargo.toml index ce3d1e7..1adc4f3 100644 --- a/wire/Cargo.toml +++ b/wire/Cargo.toml @@ -20,7 +20,7 @@ hal_v2 = [] [dependencies] ciborium = { version = "^0.2.2", default-features = false } ciborium-io = "^0.2.0" -coset = "0.3.3" +coset = "0.4.0" enumn = "0.1.4" kmr-derive = "*" log = "^0.4" From fefd840096f829d2e9e6bdc16e7f9283aa16fa44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Oct 2025 16:36:43 +0800 Subject: [PATCH 27/46] chore(deps): update syscalls requirement from 0.6.15 to 0.7.0 (#1) Updates the requirements on [syscalls](https://github.com/jasonwhite/syscalls) to permit the latest version. - [Changelog](https://github.com/jasonwhite/syscalls/blob/main/CHANGELOG.md) - [Commits](https://github.com/jasonwhite/syscalls/compare/0.6.15...0.7.0) --- updated-dependencies: - dependency-name: syscalls dependency-version: 0.7.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 481a807..4626548 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ path = "src/main.rs" [dependencies] jni = "0.21.1" -syscalls = "0.6.15" +syscalls = "0.7.0" kmr-derive = { path = "derive" } kmr-common = { path = "common" } kmr-crypto-boring = { path = "boringssl" } From ef1df14f1405471ccddaf387489941e59167c1af Mon Sep 17 00:00:00 2001 From: qwq233 Date: Mon, 20 Oct 2025 20:09:46 +0800 Subject: [PATCH 28/46] fix: unable install from root manager Signed-off-by: qwq233 --- template/customize.sh | 11 +++++------ template/daemon | 2 ++ template/service.sh | 2 -- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/template/customize.sh b/template/customize.sh index 684cca5..263862a 100644 --- a/template/customize.sh +++ b/template/customize.sh @@ -1,10 +1,9 @@ # shellcheck disable=SC2034 SKIPUNZIP=1 -DEBUG=@DEBUG@ -SONAME=@SONAME@ -SUPPORTED_ABIS="@SUPPORTED_ABIS@" -MIN_SDK=@MIN_SDK@ +SONAME="Oh My Keymint" +SUPPORTED_ABIS="arm64" +MIN_SDK=29 if [ "$BOOTMODE" ] && [ "$KSU" ]; then ui_print "- Installing from KernelSU app" @@ -72,10 +71,10 @@ chmod 755 "$MODPATH/daemon" # if [ "$ARCH" = "x64" ]; then # ui_print "- Extracting x64 libraries" -# extract "$ZIPFILE" "lib/x86_64/keymint" "$MODPATH" true +# extract "$ZIPFILE" "libs/x86_64/keymint" "$MODPATH" true # else ui_print "- Extracting arm64 libraries" - extract "$ZIPFILE" "lib/arm64-v8a/keymint" "$MODPATH" true + extract "$ZIPFILE" "libs/arm64-v8a/keymint" "$MODPATH" true # fi chmod 755 "$MODPATH/keymint" diff --git a/template/daemon b/template/daemon index a09d291..d7716c3 100644 --- a/template/daemon +++ b/template/daemon @@ -1,5 +1,7 @@ #!/system/bin/sh +export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/vendor/lib64/:/system/lib64/:/apex/com.android.runtime/lib64/bionic/" + if [ -f "/data/adb/omk/keymint" ]; then chmod 0755 "/data/adb/omk/keymint" exec "/data/adb/omk/keymint" "$@" diff --git a/template/service.sh b/template/service.sh index 9acbb09..c5983f9 100644 --- a/template/service.sh +++ b/template/service.sh @@ -1,5 +1,3 @@ -DEBUG=@DEBUG@ - MODDIR=${0%/*} cd $MODDIR From 4dbba24e4c2683d92ca0d545fba739e4a1fc3c8f Mon Sep 17 00:00:00 2001 From: qwq233 Date: Thu, 23 Oct 2025 12:53:49 +0800 Subject: [PATCH 29/46] docs: update readme Signed-off-by: qwq233 --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b91a710..c38ed5f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,61 @@ Custom keystore implementation for Android Keystore Spoofer > [!WARNING] -> This is a toy project. No ANY guarantees are made regarding performance or stability. +> The program is still in its early stages of development. +> +> No ANY guarantees are made regarding performance or stability. + +# What is this? + +This is a complete implementation of the Keystore, implementing features not implemented in the Tricky Store. +You can think of it as DLC for the game. It can work without it, but it's usually better to have it. + +In the future, we will gradually move away from the Tricky Store as a backend. + +# Install and configure + +1. Install the [qwq233's Tricky Store](https://github.com/qwq233/TrickyStore) (My fork). + +2. Install this module. + +3. Configure (if you need) + +Configuration file is located at `/data/adb/omk/config.toml` + +```toml +[main] +# We can only use Tricky Store as backend at this point. +backend = "TrickyStore" + +# The following values ​​are used to generate the seed for device encryption +# and verification. Please be sure to save the following values. If you lose +# them for some reason, please clear the module database (/data/adb/omk/data/) +# DO NOT MODIFY ANY VALUE BELOW IF YOU DO NOT UNDERSTAND WHAT ARE YOU DOING +[crypto] +root_kek_seed = "4b61c4b3bdf72bb700c351e020270846fb67ba3885e5fb67547e626af5cc1a7f" +kak_seed = "d6fa5bb024540928a7d554ab5831a0553dd2f688f5d6cb3cb1645be2ff49e357" + +[trust] +os_version = 15 +security_patch = "2025-05-01" +vb_key = "b114f5162ca0e4b4fc0544a218953caba54f3102f5f3a9346e220c770890b93b" +vb_hash = "2b38cf298eb4ca0d2dbaab32721dea2bb297b42652f4fff9180c48e7ac4da887" +verified_boot_state = true +device_locked = true + +[device] +brand = "Google" +device = "generic" +product = "generic" +manufacturer = "Google" +model = "generic" +serial = "ABC12345678ABC" +meid = "1234567890" +imei = "1234567890" +imei2 = "1234567890" +``` + +4. Enjoy # License From 328535cf5db01e6f10caf2af296f020fff940afd Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 00:04:46 +0800 Subject: [PATCH 30/46] docs: add readme and license to template Signed-off-by: qwq233 --- template/AOSP.Apache-license-2.0.txt | 202 +++++++++++++++++++++++++++ template/LICENSE.1 | 1 + template/LICENSE.2 | 1 + template/README.md | 1 + template/sepolicy.rule | 5 + 5 files changed, 210 insertions(+) create mode 100644 template/AOSP.Apache-license-2.0.txt create mode 120000 template/LICENSE.1 create mode 120000 template/LICENSE.2 create mode 120000 template/README.md diff --git a/template/AOSP.Apache-license-2.0.txt b/template/AOSP.Apache-license-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/template/AOSP.Apache-license-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/template/LICENSE.1 b/template/LICENSE.1 new file mode 120000 index 0000000..7eabdb1 --- /dev/null +++ b/template/LICENSE.1 @@ -0,0 +1 @@ +../LICENSE.md \ No newline at end of file diff --git a/template/LICENSE.2 b/template/LICENSE.2 new file mode 120000 index 0000000..8945da6 --- /dev/null +++ b/template/LICENSE.2 @@ -0,0 +1 @@ +../LICENSE-2 \ No newline at end of file diff --git a/template/README.md b/template/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/template/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/template/sepolicy.rule b/template/sepolicy.rule index c1d02a4..ee37842 100644 --- a/template/sepolicy.rule +++ b/template/sepolicy.rule @@ -1,4 +1,9 @@ allow keystore system_file unix_dgram_socket * allow system_file keystore unix_dgram_socket * allow keystore system_file file * + +allow keystore adb_data_file unix_dgram_socket * +allow adb_data_file keystore unix_dgram_socket * +allow keystore adb_data_file file * + allow crash_dump keystore process * From c87d174440356eb11c43077bfdf510db7fd206a2 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 00:06:02 +0800 Subject: [PATCH 31/46] fix: use Keystore UID (1017) this might fixes get aaid issue Signed-off-by: qwq233 --- README.md | 4 ++-- build.rs | 1 + src/config.rs | 2 +- src/keymaster/db.rs | 2 +- src/keymint/sdd.rs | 2 +- src/main.rs | 30 ++++++++++++++++++------------ src/plat/utils.rs | 12 ++++++------ 7 files changed, 30 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index c38ed5f..9cfee8a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ In the future, we will gradually move away from the Tricky Store as a backend. 3. Configure (if you need) -Configuration file is located at `/data/adb/omk/config.toml` +Configuration file is located at `/data/misc/keystore/omk/config.toml` ```toml [main] @@ -31,7 +31,7 @@ backend = "TrickyStore" # The following values ​​are used to generate the seed for device encryption # and verification. Please be sure to save the following values. If you lose -# them for some reason, please clear the module database (/data/adb/omk/data/) +# them for some reason, please clear the module database (/data/misc/keystore/omk/data/) # DO NOT MODIFY ANY VALUE BELOW IF YOU DO NOT UNDERSTAND WHAT ARE YOU DOING [crypto] root_kek_seed = "4b61c4b3bdf72bb700c351e020270846fb67ba3885e5fb67547e626af5cc1a7f" diff --git a/build.rs b/build.rs index 30e9570..e2509c4 100644 --- a/build.rs +++ b/build.rs @@ -29,6 +29,7 @@ fn main() { .output(PathBuf::from("aidl.rs")); let dirs = vec![ + "aidl/android/content/pm", "aidl/android/system/keystore2", "aidl/android/hardware/security/keymint", "aidl/android/security/metrics", diff --git a/src/config.rs b/src/config.rs index 20801f0..0ea0fe6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,7 +11,7 @@ lazy_static::lazy_static! { } #[cfg(target_os = "android")] -const CONFIG_PATH: &str = "/data/adb/omk/config.toml"; +const CONFIG_PATH: &str = "/data/misc/keystore/omk/config.toml"; #[cfg(not(target_os = "android"))] const CONFIG_PATH: &str = "./omk/config.toml"; diff --git a/src/keymaster/db.rs b/src/keymaster/db.rs index 3f38278..db057e3 100644 --- a/src/keymaster/db.rs +++ b/src/keymaster/db.rs @@ -31,7 +31,7 @@ use TransactionBehavior::Immediate; const DB_ROOT_PATH: &str = "./omk/data"; #[cfg(target_os = "android")] -const DB_ROOT_PATH: &str = "/data/adb/omk/data"; +const DB_ROOT_PATH: &str = "/data/misc/keystore/omk/data"; const PERSISTENT_DB_FILENAME: &'static str = "keymaster.db"; diff --git a/src/keymint/sdd.rs b/src/keymint/sdd.rs index b8607ac..65af5a3 100644 --- a/src/keymint/sdd.rs +++ b/src/keymint/sdd.rs @@ -31,7 +31,7 @@ use std::path; const SECURE_DELETION_DATA_FILE: &str = "./omk/data/keymint.dat"; #[cfg(target_os = "android")] -const SECURE_DELETION_DATA_FILE: &str = "/data/adb/omk/data/keymint.dat"; +const SECURE_DELETION_DATA_FILE: &str = "/data/misc/keystore/omk/data/keymint.dat"; fn read_sdd_file() -> Result { let f = fs::File::open(SECURE_DELETION_DATA_FILE).map_err(|e| { diff --git a/src/main.rs b/src/main.rs index c033269..931144f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::panic; -use log::{debug, error}; +use log::{debug, error, info}; use rsbinder::hub; use crate::{ @@ -36,11 +36,17 @@ const TAG: &str = "OhMyKeymint"; fn main() { logging::init_logger(); - debug!("Hello, OhMyKeymint!"); - debug!("Reading config"); + info!("Hello, OhMyKeymint!"); + info!("Reading config"); + + unsafe { + info!("Setting UID to KEYSTORE_UID (1017)"); + libc::setuid(1017); // KEYSTORE_UID + } + let config = CONFIG.read().unwrap(); - debug!("Initial process state"); + info!("Initial process state"); rsbinder::ProcessState::init_default(); // Redirect panic messages to logcat. @@ -48,30 +54,30 @@ fn main() { error!("{}", panic_info); })); - debug!("Starting thread pool"); + info!("Starting thread pool"); rsbinder::ProcessState::start_thread_pool(); match config.main.backend { Backend::OMK => { - debug!("Using OhMyKeymint backend"); - debug!("Creating keystore service"); + info!("Using OhMyKeymint backend"); + info!("Creating keystore service"); let dev = KeystoreService::new_native_binder().unwrap(); let service = BnKeystoreService::new_binder(dev); - debug!("Adding keystore service to hub"); + info!("Adding keystore service to hub"); hub::add_service("keystore3", service.as_binder()).unwrap(); } Backend::TrickyStore => { - debug!("Using TrickyStore backend"); - debug!("Creating keystore service"); + info!("Using TrickyStore backend"); + info!("Creating keystore service"); let dev = KeystoreService::new_native_binder().unwrap(); - debug!("Adding OMK service to hub"); + info!("Adding OMK service to hub"); let service = BnOhMyKsService::new_binder(dev); hub::add_service("omk", service.as_binder()).unwrap(); } } - debug!("Joining thread pool"); + info!("Joining thread pool"); rsbinder::ProcessState::join_thread_pool().unwrap(); } diff --git a/src/plat/utils.rs b/src/plat/utils.rs index 220139a..6690962 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -106,13 +106,13 @@ pub fn get_aaid(uid: u32) -> anyhow::Result> { let current_uid = unsafe { libc::getuid() }; let current_euid = unsafe { libc::geteuid() }; debug!("Current UID: {}, EUID: {}", current_uid, current_euid); - unsafe { - libc::seteuid(1017); // KEYSTORE_UID - } + // unsafe { + // libc::seteuid(1017); // KEYSTORE_UID + // } let result = pm.getKeyAttestationApplicationId(uid as i32); - unsafe { - libc::seteuid(current_euid); - } + // unsafe { + // libc::seteuid(current_euid); + // } result }; if let Result::Ok(application_id) = result { From ee203395000b48489479de609ae568483a6e43f9 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 00:49:57 +0800 Subject: [PATCH 32/46] fix: retrieve apex info by getActivePackages The original implementation is inconsistent with the native AOSP implementation. This commit fixes this problem to ensure consistency of the results. Signed-off-by: qwq233 --- src/global.rs | 8 ++++++-- src/keymaster/apex.rs | 6 +++--- src/keymaster/keymint_device.rs | 21 +++++++++++---------- src/keymaster/service.rs | 14 +++++++++++--- src/main.rs | 18 ++++++++++++------ src/plat/utils.rs | 7 +++++-- 6 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/global.rs b/src/global.rs index e54f477..127da02 100644 --- a/src/global.rs +++ b/src/global.rs @@ -11,14 +11,18 @@ use crate::{ android::hardware::security::secureclock::ISecureClock::ISecureClock, err, keymaster::{ - async_task::AsyncTask, db::KeymasterDb, enforcements::Enforcements, gc::Gc, - keymint_device::get_keymint_wrapper, super_key::SuperKeyManager, + apex::ApexModuleInfo, async_task::AsyncTask, db::KeymasterDb, enforcements::Enforcements, gc::Gc, keymint_device::get_keymint_wrapper, super_key::SuperKeyManager }, watchdog as wd, }; static DB_INIT: Once = Once::new(); + +lazy_static::lazy_static! { + pub static ref APEX_MODULE_HASH: anyhow::Result> = crate::plat::utils::get_apex_module_info(); +} + /// A single on-demand worker thread that handles deferred tasks with two different /// priorities. pub static ASYNC_TASK: LazyLock> = LazyLock::new(Default::default); diff --git a/src/keymaster/apex.rs b/src/keymaster/apex.rs index a9b0f5a..8119736 100644 --- a/src/keymaster/apex.rs +++ b/src/keymaster/apex.rs @@ -5,7 +5,7 @@ use der::{ DerOrd, Encode, Sequence, }; -#[derive(Sequence, Debug)] +#[derive(Sequence, Debug, Clone)] pub struct ApexModuleInfo { pub package_name: OctetString, pub version_code: u64, @@ -23,6 +23,6 @@ impl DerOrd for ApexModuleInfo { } } -pub fn encode_module_info(module_info: Vec) -> Result, der::Error> { - SetOfVec::::from_iter(module_info.into_iter())?.to_der() +pub fn encode_module_info(module_info: &Vec) -> Result, der::Error> { + SetOfVec::::from_iter(module_info.iter().cloned())?.to_der() } diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index a4eaeb8..b50b0c4 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -94,7 +94,7 @@ impl KeyMintDevice { /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] pub fn get(security_level: SecurityLevel) -> Result { let km_dev = KeyMintWrapper::new(security_level) - .expect(err!("Failed to init strongbox wrapper").as_str()); + .expect(err!("Failed to init strongbox wrapper").as_str()); let hw_info = km_dev.get_hardware_info().unwrap(); let km_uuid = RwLock::new(Uuid::from(security_level)); @@ -447,10 +447,8 @@ impl IKeyMintDevice for KeyMintWrapper { crate::android::hardware::security::keymint::KeyMintHardwareInfo::KeyMintHardwareInfo, Status, > { - let hardware_info: keymint::KeyMintHardwareInfo = self.keymint - .lock().unwrap() - .get_hardware_info() - .unwrap(); + let hardware_info: keymint::KeyMintHardwareInfo = + self.keymint.lock().unwrap().get_hardware_info().unwrap(); let resp = crate::android::hardware::security::keymint::KeyMintHardwareInfo::KeyMintHardwareInfo { @@ -527,8 +525,7 @@ impl IKeyMintDevice for KeyMintWrapper { key_params: key_parameters, attestation_key, }); - let result = self.keymint.lock().unwrap() - .process_req(req); + let result = self.keymint.lock().unwrap().process_req(req); if let None = result.rsp { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -932,7 +929,9 @@ impl KeyMintWrapper { } pub fn get_hardware_info(&self) -> Result { - self.keymint.lock().unwrap() + self.keymint + .lock() + .unwrap() .get_hardware_info() .map_err(|_| Error::Km(ErrorCode::UNKNOWN_ERROR)) } @@ -1199,7 +1198,7 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { sha256: Box::new(kmr_crypto_boring::sha256::BoringSha256), }; - let keys: Box = Box::new(soft::Keys::new( + let keys: Box = Box::new(soft::Keys::new( config.crypto.root_kek_seed.clone(), config.crypto.kak_seed.clone(), )); @@ -1267,7 +1266,9 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { let module_hash = crate::global::ENCODED_MODULE_INFO.get_or_try_init(|| -> Result, anyhow::Error> { - let apex_info = crate::plat::utils::get_apex_module_info()?; + let apex_info = crate::global::APEX_MODULE_HASH + .as_ref() + .map_err(|_| anyhow::anyhow!("Failed to get APEX module info."))?; let encoding = encode_module_info(apex_info) .map_err(|_| anyhow::anyhow!("Failed to encode module info."))?; diff --git a/src/keymaster/service.rs b/src/keymaster/service.rs index 7980f12..1dd2a39 100644 --- a/src/keymaster/service.rs +++ b/src/keymaster/service.rs @@ -468,7 +468,9 @@ impl KeystoreService { Tag::MODULE_HASH => { let info = ENCODED_MODULE_INFO.get_or_try_init(|| -> Result, anyhow::Error> { - let apex_info = crate::plat::utils::get_apex_module_info()?; + let apex_info = crate::global::APEX_MODULE_HASH + .as_ref() + .map_err(|_| anyhow::anyhow!("Failed to get APEX module info."))?; let encoding = encode_module_info(apex_info) .map_err(|_| anyhow::anyhow!("Failed to encode module info."))?; @@ -788,9 +790,15 @@ impl IOhMyKsService for KeystoreService { .unwrap_or(()); // reset KeyMintTa - get_keymint_wrapper(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap().reset_keymint_ta().unwrap(); + get_keymint_wrapper(SecurityLevel::TRUSTED_ENVIRONMENT) + .unwrap() + .reset_keymint_ta() + .unwrap(); - get_keymint_wrapper(SecurityLevel::STRONGBOX).unwrap().reset_keymint_ta().unwrap(); + get_keymint_wrapper(SecurityLevel::STRONGBOX) + .unwrap() + .reset_keymint_ta() + .unwrap(); Result::Ok(()) } diff --git a/src/main.rs b/src/main.rs index 931144f..2e636ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,14 @@ use std::panic; +use lazy_static::lazy_static; use log::{debug, error, info}; use rsbinder::hub; use crate::{ android::system::keystore2::IKeystoreService::BnKeystoreService, config::{Backend, CONFIG}, - keymaster::service::KeystoreService, + keymaster::{apex::ApexModuleInfo, service::KeystoreService}, top::qwq2333::ohmykeymint::IOhMyKsService::BnOhMyKsService, }; @@ -38,17 +39,22 @@ fn main() { logging::init_logger(); info!("Hello, OhMyKeymint!"); info!("Reading config"); + let config = CONFIG.read().unwrap(); + + info!("Initial process state"); + rsbinder::ProcessState::init_default(); + + // We can no longer retrieve APEX module info after dropping privileges. + debug!("Retrieving APEX module hash with root privileges"); + if let Err(e) = global::APEX_MODULE_HASH.as_ref() { + error!("Failed to retrieve APEX module info: {:?}", e); + } unsafe { info!("Setting UID to KEYSTORE_UID (1017)"); libc::setuid(1017); // KEYSTORE_UID } - let config = CONFIG.read().unwrap(); - - info!("Initial process state"); - rsbinder::ProcessState::init_default(); - // Redirect panic messages to logcat. panic::set_hook(Box::new(|panic_info| { error!("{}", panic_info); diff --git a/src/plat/utils.rs b/src/plat/utils.rs index 6690962..e7d0f81 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -188,8 +188,11 @@ fn encode_application_id( pub fn get_apex_module_info() -> anyhow::Result> { let apex = get_apex()?; let result: Vec = apex - .getAllPackages() - .map_err(|e| anyhow::anyhow!(err!("getAllPackages failed: {:?}", e)))?; + .getActivePackages() + .map_err(|e| { + log::error!("Failed to get active packages: {:?}", e); + anyhow::anyhow!(err!("getActivePackages failed: {:?}", e)) + })?; let result: Vec = result .iter() From 13f72d556afb78b73498e9ccf51f280c08db1003 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 00:50:15 +0800 Subject: [PATCH 33/46] chore: format code Signed-off-by: qwq233 --- src/global.rs | 4 ++-- src/keymaster/security_level.rs | 16 ++++++++-------- src/keymaster/utils.rs | 4 ++-- src/logging.rs | 6 +++++- src/plat/utils.rs | 5 ++--- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/global.rs b/src/global.rs index 127da02..82c9f83 100644 --- a/src/global.rs +++ b/src/global.rs @@ -11,14 +11,14 @@ use crate::{ android::hardware::security::secureclock::ISecureClock::ISecureClock, err, keymaster::{ - apex::ApexModuleInfo, async_task::AsyncTask, db::KeymasterDb, enforcements::Enforcements, gc::Gc, keymint_device::get_keymint_wrapper, super_key::SuperKeyManager + apex::ApexModuleInfo, async_task::AsyncTask, db::KeymasterDb, enforcements::Enforcements, + gc::Gc, keymint_device::get_keymint_wrapper, super_key::SuperKeyManager, }, watchdog as wd, }; static DB_INIT: Once = Once::new(); - lazy_static::lazy_static! { pub static ref APEX_MODULE_HASH: anyhow::Result> = crate::plat::utils::get_apex_module_info(); } diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 36eb93b..fdbd858 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -19,14 +19,14 @@ use crate::{ err, global::{DB, ENFORCEMENTS, SUPER_KEY, UNDEFINED_NOT_AFTER}, keymaster::{ - attestation_key_utils::{AttestationKeyInfo, get_attest_key_info}, + attestation_key_utils::{get_attest_key_info, AttestationKeyInfo}, db::{ BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, SubComponentType, Uuid, }, - error::{KsError, into_logged_binder, map_binder_status}, - keymint_device::{KeyMintWrapper}, + error::{into_logged_binder, map_binder_status, KsError}, + keymint_device::KeyMintWrapper, metrics_store::log_key_creation_event_stats, operation::{KeystoreOperation, LoggingInfo, OperationDb}, super_key::{KeyBlob, SuperKeyManager}, @@ -60,8 +60,8 @@ pub struct KeystoreSecurityLevel { impl KeystoreSecurityLevel { pub fn new(security_level: SecurityLevel, km_uuid: Uuid) -> Result { let km_wrapper = KeyMintWrapper::new(security_level) - .expect(err!("Failed to init strongbox wrapper").as_str()); - + .expect(err!("Failed to init strongbox wrapper").as_str()); + let hw_info = km_wrapper .get_hardware_info() .context(err!("Failed to get hardware info."))?; @@ -466,7 +466,8 @@ impl KeystoreSecurityLevel { ), 5000, // Generate can take a little longer. ); - let result = self.get_keymint_wrapper() + let result = self + .get_keymint_wrapper() .generateKey(¶ms, attest_key.as_ref()); map_binder_status(result) }, @@ -485,8 +486,7 @@ impl KeystoreSecurityLevel { ), 5000, // Generate can take a little longer. ); - self.get_keymint_wrapper() - .generateKey(¶ms, None) + self.get_keymint_wrapper().generateKey(¶ms, None) } .context(err!( "While generating without a provided \ diff --git a/src/keymaster/utils.rs b/src/keymaster/utils.rs index e942ab1..ee2d6b5 100644 --- a/src/keymaster/utils.rs +++ b/src/keymaster/utils.rs @@ -6,9 +6,9 @@ use crate::{ }, consts, err, keymaster::{ - error::{KsError as Error, map_ks_error}, + error::{map_ks_error, KsError as Error}, key_parameter::KeyParameter, - keymint_device::{KeyMintDevice, get_keymint_wrapper}, + keymint_device::{get_keymint_wrapper, KeyMintDevice}, }, watchdog, }; diff --git a/src/logging.rs b/src/logging.rs index 212caab..0caf184 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -37,5 +37,9 @@ pub fn init_logger() { let log4rs = log4rs::Logger::new(config); - multi_log::MultiLogger::init(vec![Box::new(android_logger), Box::new(log4rs)], log::Level::Debug).unwrap(); + multi_log::MultiLogger::init( + vec![Box::new(android_logger), Box::new(log4rs)], + log::Level::Debug, + ) + .unwrap(); } diff --git a/src/plat/utils.rs b/src/plat/utils.rs index e7d0f81..fa80a8f 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -187,9 +187,8 @@ fn encode_application_id( pub fn get_apex_module_info() -> anyhow::Result> { let apex = get_apex()?; - let result: Vec = apex - .getActivePackages() - .map_err(|e| { + let result: Vec = + apex.getActivePackages().map_err(|e| { log::error!("Failed to get active packages: {:?}", e); anyhow::anyhow!(err!("getActivePackages failed: {:?}", e)) })?; From 0fc5ba35d46110d4975274d183325aafc048ee88 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 01:41:03 +0800 Subject: [PATCH 34/46] fix: weird ci issue Signed-off-by: qwq233 --- build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/build.rs b/build.rs index e2509c4..d1a2ef9 100644 --- a/build.rs +++ b/build.rs @@ -67,6 +67,7 @@ fn main() { .replace("HardwareAuthenticatorType.", "super::super::super::hardware::security::keymint::HardwareAuthenticatorType::HardwareAuthenticatorType::") .replace("r#authenticatorType: super::Digest::Digest::NONE,", "r#authenticatorType: super::HardwareAuthenticatorType::HardwareAuthenticatorType::NONE,") .replace("r#authenticatorType: super::KeyPermission::KeyPermission::NONE,", "r#authenticatorType: super::HardwareAuthenticatorType::HardwareAuthenticatorType::NONE,") + .replace("r#authenticatorType: super::PaddingMode::PaddingMode::NONE,", "r#authenticatorType: super::HardwareAuthenticatorType::HardwareAuthenticatorType::NONE,") .replace("r#operation: rsbinder::Strong,", "r#operation: Option>,"); println!("Patched AIDL content:\n{}", generated_path.display()); From 7b1fff74c6f9dcfdc1886082784286affbb61bd2 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 23:07:36 +0800 Subject: [PATCH 35/46] fix: missing parameters due to improper implementation of boringssl Signed-off-by: qwq233 --- boringssl/src/ec.rs | 27 ++++++++++++++++++++++++--- boringssl/src/lib.rs | 9 +++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/boringssl/src/ec.rs b/boringssl/src/ec.rs index 00a3054..c963373 100644 --- a/boringssl/src/ec.rs +++ b/boringssl/src/ec.rs @@ -33,16 +33,37 @@ use openssl::hash::MessageDigest; use Box; use Vec; -#[cfg(soong)] +#[cfg(target_os = "android")] fn private_key_from_der_for_group( der: &[u8], group: &openssl::ec::EcGroupRef, ) -> Result, openssl::error::ErrorStack> { // This method is an Android modification to the rust-openssl crate. - openssl::ec::EcKey::private_key_from_der_for_group(der, group) + ossl_private_key_from_der_for_group(der, group) +} + +#[cfg(target_os = "android")] +fn ossl_private_key_from_der_for_group( + der: &[u8], + group: &openssl::ec::EcGroupRef, +) -> Result, openssl::error::ErrorStack> { + use foreign_types::ForeignTypeRef; + + unsafe { + let mut cbs = ffi::CBS { + data: der.as_ptr(), + len: der.len(), + }; + + crate::ocvt_p(ffi::EC_KEY_parse_private_key( + &mut cbs as *mut ffi::CBS, + group.as_ptr(), + )) + .map(|p| openssl::ec::EcKey::from_ptr(p)) + } } -#[cfg(not(soong))] +#[cfg(not(target_os = "android"))] fn private_key_from_der_for_group( der: &[u8], _group: &openssl::ec::EcGroupRef, diff --git a/boringssl/src/lib.rs b/boringssl/src/lib.rs index 556dbef..b174794 100644 --- a/boringssl/src/lib.rs +++ b/boringssl/src/lib.rs @@ -153,6 +153,15 @@ fn cvt_p(r: *mut T) -> Result<*mut T, Error> { } } +#[inline] +fn ocvt_p(r: *mut T) -> Result<*mut T, openssl::error::ErrorStack> { + if r.is_null() { + Err(openssl::error::ErrorStack::get()) + } else { + Ok(r) + } +} + #[inline] fn cvt(r: libc::c_int) -> Result { if r <= 0 { From 26f04a57d9a7566bd0bf5c825f459c4f532f91cd Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 23:23:36 +0800 Subject: [PATCH 36/46] feat: grant necessary permission for attestation Signed-off-by: qwq233 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f880c5c..3fbf678 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,9 @@ jobs: runs-on: ubuntu-latest needs: - build-boringssl + permissions: + id-token: write + attestations: write strategy: matrix: build-type: [debug, release] From 7e793979d1a98a01e13e03be394129735c6dd1fd Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 24 Oct 2025 23:34:55 +0800 Subject: [PATCH 37/46] fix: unmatched format for changelog generator Signed-off-by: qwq233 --- .github/workflows/update-changelog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index d33a383..091e84e 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -35,7 +35,7 @@ jobs: echo "$RELEASE_BODY" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - ASSET_URL=$(echo "$ASSETS_JSON" | jq -r '.[] | select(.name | endswith("release.zip")) | .browser_download_url' | head -n1) + ASSET_URL=$(echo "$ASSETS_JSON" | jq -r '.[] | select(.name | contains("release")) | .browser_download_url' | head -n1) if [ -z "$ASSET_URL" ]; then echo "::error::No release zip found in assets! Names:" From ea42e2c4f2e4300ae75d9e597e5dba51bb5fe21a Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sun, 26 Oct 2025 03:12:04 +0800 Subject: [PATCH 38/46] fix: allow overwrite attest property this allows modify device properties. i.e: `IMEI` Signed-off-by: qwq233 --- common/src/keyblob.rs | 2 +- common/src/tag.rs | 26 ++++++++++++++++++++++++++ src/att_mgr.rs | 31 +++++++++++++++++-------------- ta/src/cert.rs | 32 ++++++++++++++++++-------------- ta/src/keys.rs | 7 ++++++- 5 files changed, 68 insertions(+), 30 deletions(-) diff --git a/common/src/keyblob.rs b/common/src/keyblob.rs index 600b036..c8ee21d 100644 --- a/common/src/keyblob.rs +++ b/common/src/keyblob.rs @@ -187,7 +187,7 @@ pub struct SecureDeletionData { } /// Indication of what kind of key operation requires a secure deletion slot. -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SlotPurpose { /// Secure deletion slot needed for key generation. KeyGeneration, diff --git a/common/src/tag.rs b/common/src/tag.rs index 96033ea..87b5fa7 100644 --- a/common/src/tag.rs +++ b/common/src/tag.rs @@ -108,6 +108,32 @@ macro_rules! get_opt_tag_value { } } +#[macro_export] +macro_rules! modify_tag_value { + { $params:expr, $variant:ident, $new_value:expr } => { + modify_tag_value!($params, $variant, $new_value, InvalidTag) + }; + { $params:expr, $variant:ident, $new_value:expr, $dup_error:ident } => { + { + let mut found = false; + for param in $params.iter_mut() { + if let kmr_wire::keymint::KeyParam::$variant(v) = param { + if (found) { + return Err($crate::km_err!(InvalidTag, "duplicate tag {}", stringify!($variant))); + } + *v = $new_value; + found = true; + } + } + if found { + Ok(()) + } else { + Err($crate::km_err!(InvalidTag, "missing tag {}", stringify!($variant))) + } + } + }; +} + /// Macro to retrieve a `bool` tag value, returning `false` if the tag is absent #[macro_export] macro_rules! get_bool_tag_value { diff --git a/src/att_mgr.rs b/src/att_mgr.rs index 757dd74..2e4a316 100644 --- a/src/att_mgr.rs +++ b/src/att_mgr.rs @@ -8,21 +8,24 @@ pub struct AttestationIdMgr; impl RetrieveAttestationIds for AttestationIdMgr { fn get(&self) -> Result { - let config = CONFIG - .read() - .map_err(|_| km_err!(UnknownError, "config lock poisoned"))?; + let info = { + let config = CONFIG + .read() + .map_err(|_| km_err!(UnknownError, "config lock poisoned"))?; + AttestationIdInfo { + brand: config.device.brand.clone().into_bytes(), + device: config.device.device.clone().into_bytes(), + product: config.device.product.clone().into_bytes(), + serial: config.device.serial.clone().into_bytes(), + imei: config.device.imei.clone().into_bytes(), + imei2: config.device.imei2.clone().into_bytes(), + meid: config.device.meid.clone().into_bytes(), + manufacturer: config.device.manufacturer.clone().into_bytes(), + model: config.device.model.clone().into_bytes(), + } + }; - Ok(AttestationIdInfo { - brand: config.device.brand.clone().into_bytes(), - device: config.device.device.clone().into_bytes(), - product: config.device.product.clone().into_bytes(), - serial: config.device.serial.clone().into_bytes(), - imei: config.device.imei.clone().into_bytes(), - imei2: config.device.imei2.clone().into_bytes(), - meid: config.device.meid.clone().into_bytes(), - manufacturer: config.device.manufacturer.clone().into_bytes(), - model: config.device.model.clone().into_bytes(), - }) + Ok(info) } fn destroy_all(&mut self) -> Result<(), kmr_common::Error> { diff --git a/ta/src/cert.rs b/ta/src/cert.rs index 52e5efb..7148f0a 100644 --- a/ta/src/cert.rs +++ b/ta/src/cert.rs @@ -28,7 +28,7 @@ use kmr_common::crypto::KeyMaterial; use kmr_common::{ crypto, der_err, get_tag_value, km_err, tag, try_to_vec, vec_try_with_capacity, Error, }; -use kmr_common::{get_bool_tag_value, get_opt_tag_value, FallibleAllocExt}; +use kmr_common::{get_bool_tag_value, get_opt_tag_value, modify_tag_value, FallibleAllocExt}; use kmr_wire::{ keymint, keymint::{ @@ -445,10 +445,12 @@ macro_rules! check_attestation_id { match $mustmatch { None => return Err(km_err!(CannotAttestIds, "no attestation IDs provisioned")), - Some(want) => if val != want { - return Err(km_err!(CannotAttestIds, - "attestation ID mismatch for {}", - stringify!($variant))) + Some(want) => if val != want.as_slice() { + let result = modify_tag_value!($params, $variant, want.to_vec()); + if result.is_err() { + return Err(result.unwrap_err()); + } + log::info!("attestation ID mismatch for {}, reseting to {:?}", stringify!($variant), want); } } } @@ -467,48 +469,50 @@ impl<'a> AuthorizationList<'a> { app_id: Option<&'a [u8]>, additional_attestation_info: &'a [KeyParam], ) -> Result { + let mut keygen_params = keygen_params.to_vec(); + check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdBrand, attestation_ids.map(|v| &v.brand) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdDevice, attestation_ids.map(|v| &v.device) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdProduct, attestation_ids.map(|v| &v.product) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdSerial, attestation_ids.map(|v| &v.serial) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdImei, attestation_ids.map(|v| &v.imei) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdSecondImei, attestation_ids.map(|v| &v.imei2) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdMeid, attestation_ids.map(|v| &v.meid) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdManufacturer, attestation_ids.map(|v| &v.manufacturer) ); check_attestation_id!( - keygen_params, + &mut keygen_params, AttestationIdModel, attestation_ids.map(|v| &v.model) ); diff --git a/ta/src/keys.rs b/ta/src/keys.rs index 862e622..61ccbb7 100644 --- a/ta/src/keys.rs +++ b/ta/src/keys.rs @@ -30,7 +30,7 @@ use kmr_wire::{ }, *, }; -use log::{error, warn}; +use log::{debug, error, warn}; use spki::SubjectPublicKeyInfoOwned; use std::collections::btree_map::Entry; use x509_cert::ext::pkix::KeyUsages; @@ -399,6 +399,11 @@ impl crate::KeyMintTa { key_material: KeyMaterial, purpose: keyblob::SlotPurpose, ) -> Result { + debug!("finish_keyblob_creation: params {:?} attestKey {:?}", params, attestation_key); + debug!("key characteristics: {:?}", chars); + debug!("key material: {:?}", key_material); + debug!("keyblob purpose: {:?}", purpose); + let keyblob = keyblob::PlaintextKeyBlob { // Don't include any `SecurityLevel::Keystore` characteristics in the set that is bound // to the key. From a4637ba6af489337954964e8f9a24035903a957d Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sun, 26 Oct 2025 03:13:55 +0800 Subject: [PATCH 39/46] fix: unable to set sw enforced property Signed-off-by: qwq233 --- src/keymaster/keymint_device.rs | 64 ++++++--------------------------- src/keymaster/security_level.rs | 4 +-- src/keymaster/utils.rs | 7 +--- 3 files changed, 13 insertions(+), 62 deletions(-) diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index b50b0c4..868ea0b 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -93,17 +93,13 @@ impl KeyMintDevice { /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`] pub fn get(security_level: SecurityLevel) -> Result { - let km_dev = KeyMintWrapper::new(security_level) - .expect(err!("Failed to init strongbox wrapper").as_str()); - let hw_info = km_dev.get_hardware_info().unwrap(); - let km_uuid = RwLock::new(Uuid::from(security_level)); let wrapper: KeyMintWrapper = KeyMintWrapper::new(security_level).unwrap(); Ok(KeyMintDevice { km_dev: wrapper, - version: hw_info.version_number, + version: 400, km_uuid, - security_level: get_keymaster_security_level(hw_info.security_level)?, + security_level, }) } @@ -416,12 +412,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result: InternalBeginResult = match result.rsp.unwrap() { PerformOpRsp::DeviceBegin(rsp) => rsp.ret, - _ => { - return Err(Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + _ => unreachable!("Unexpected response type") }; let operation = crate::keymaster::keymint_operation::KeyMintOperation::new( @@ -458,12 +449,7 @@ impl IKeyMintDevice for KeyMintWrapper { SecurityLevel::TRUSTED_ENVIRONMENT } kmr_wire::keymint::SecurityLevel::Strongbox => SecurityLevel::STRONGBOX, - _ => { - return Err(Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + kmr_wire::keymint::SecurityLevel::Keystore => SecurityLevel::KEYSTORE, }, versionNumber: hardware_info.version_number, keyMintName: hardware_info.key_mint_name, @@ -531,12 +517,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceGenerateKey(rsp) => rsp.ret, - _ => { - return Err(Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + _ => unreachable!("Unexpected response type") }; let resp = key_creation_result_to_aidl(result)?; @@ -595,12 +576,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceImportKey(rsp) => rsp.ret, - _ => { - return Err(Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + _ => unreachable!("Unexpected response type") }; let resp = key_creation_result_to_aidl(result)?; @@ -763,12 +739,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceConvertStorageKeyToEphemeral(rsp) => rsp.ret, - _ => { - return Err(Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + _ => unreachable!("Unexpected response type") }; Result::Ok(result) @@ -794,12 +765,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceGetKeyCharacteristics(rsp) => rsp.ret, - _ => { - return Err(Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + _ => unreachable!("Unexpected response type") }; let result: Result, rsbinder::Status> = result.iter().map(|kc| { @@ -818,12 +784,7 @@ impl IKeyMintDevice for KeyMintWrapper { SecurityLevel::TRUSTED_ENVIRONMENT } kmr_wire::keymint::SecurityLevel::Strongbox => SecurityLevel::STRONGBOX, - _ => { - return Err(rsbinder::Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + kmr_wire::keymint::SecurityLevel::Keystore => SecurityLevel::KEYSTORE, }, }) @@ -865,12 +826,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::GetRootOfTrust(rsp) => rsp.ret, - _ => { - return Err(Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + _ => unreachable!("Unexpected response type") }; Result::Ok(result) diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index fdbd858..838c1e6 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -142,7 +142,7 @@ impl KeystoreSecurityLevel { let creation_datetime = SystemTime::now(); // Add CREATION_DATETIME only if the backend version Keymint V1 (100) or newer. - if self.hw_info.version_number >= 100 { + // if self.hw_info.version_number >= 100 { result.push(KeyParameter { tag: Tag::CREATION_DATETIME, value: KeyParameterValue::DateTime( @@ -160,7 +160,7 @@ impl KeystoreSecurityLevel { ))?, ), }); - } + // } // If there is an attestation challenge we need to get an application id. if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) { diff --git a/src/keymaster/utils.rs b/src/keymaster/utils.rs index ee2d6b5..70243d9 100644 --- a/src/keymaster/utils.rs +++ b/src/keymaster/utils.rs @@ -635,12 +635,7 @@ pub fn key_creation_result_to_aidl( SecurityLevel::TRUSTED_ENVIRONMENT } kmr_wire::keymint::SecurityLevel::Strongbox => SecurityLevel::STRONGBOX, - _ => { - return Err(rsbinder::Status::new_service_specific_error( - ErrorCode::UNKNOWN_ERROR.0, - None, - )) - } + kmr_wire::keymint::SecurityLevel::Keystore => SecurityLevel::KEYSTORE, }, }) From 661510956bfe4e97b7ae6c7d0bac9ce19f7c1351 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Mon, 27 Oct 2025 02:05:12 +0800 Subject: [PATCH 40/46] chore: apply clippy suggestions Signed-off-by: qwq233 --- src/keymaster/db.rs | 2 +- src/keymaster/enforcements.rs | 2 +- src/keymaster/keymint_device.rs | 78 +++++++++++++----------------- src/keymaster/keymint_operation.rs | 10 ++-- src/keymaster/security_level.rs | 8 +-- src/keymaster/super_key.rs | 2 +- src/keymaster/utils.rs | 4 +- src/keymint/clock.rs | 2 +- src/main.rs | 3 +- src/plat/property_watcher.rs | 4 +- src/plat/utils.rs | 8 +-- src/utils.rs | 2 +- 12 files changed, 55 insertions(+), 70 deletions(-) diff --git a/src/keymaster/db.rs b/src/keymaster/db.rs index db057e3..c070b2b 100644 --- a/src/keymaster/db.rs +++ b/src/keymaster/db.rs @@ -33,7 +33,7 @@ const DB_ROOT_PATH: &str = "./omk/data"; #[cfg(target_os = "android")] const DB_ROOT_PATH: &str = "/data/misc/keystore/omk/data"; -const PERSISTENT_DB_FILENAME: &'static str = "keymaster.db"; +const PERSISTENT_DB_FILENAME: &str = "keymaster.db"; const DB_BUSY_RETRY_INTERVAL: Duration = Duration::from_micros(500); diff --git a/src/keymaster/enforcements.rs b/src/keymaster/enforcements.rs index a36c233..d73fb87 100644 --- a/src/keymaster/enforcements.rs +++ b/src/keymaster/enforcements.rs @@ -789,7 +789,7 @@ impl Enforcements { let result = Self::find_auth_token(|auth_token_entry: &AuthTokenEntry| { let token_valid = now_in_millis .checked_sub(&auth_token_entry.time_received()) - .map_or(false, |token_age_in_millis| { + .is_some_and(|token_age_in_millis| { auth_token_max_age_millis > token_age_in_millis.milliseconds() }); token_valid && auth_token_entry.satisfies(&sids, auth_type) diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index 868ea0b..51b89fd 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -171,7 +171,7 @@ impl KeyMintDevice { &BlobInfo::new(&creation_result.keyBlob, &blob_metadata), &CertificateInfo::new(None, None), &key_metadata, - &*self.km_uuid.read().unwrap(), + &self.km_uuid.read().unwrap(), ) .context(err!("store_new_key failed"))?; Ok(()) @@ -303,7 +303,7 @@ impl KeyMintDevice { F: Fn(&[u8]) -> Result, { let (f_result, upgraded_blob) = crate::keymaster::utils::upgrade_keyblob_if_required_with( - self.security_level.clone(), + self.security_level, self.version(), &key_blob, &[], @@ -407,7 +407,7 @@ impl IKeyMintDevice for KeyMintWrapper { }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result: InternalBeginResult = match result.rsp.unwrap() { @@ -416,7 +416,7 @@ impl IKeyMintDevice for KeyMintWrapper { }; let operation = crate::keymaster::keymint_operation::KeyMintOperation::new( - self.security_level.clone(), + self.security_level, result.challenge, km_params, result.op_handle, @@ -512,7 +512,7 @@ impl IKeyMintDevice for KeyMintWrapper { attestation_key, }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result = match result.rsp.unwrap() { @@ -571,7 +571,7 @@ impl IKeyMintDevice for KeyMintWrapper { attestation_key, }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result = match result.rsp.unwrap() { @@ -614,7 +614,7 @@ impl IKeyMintDevice for KeyMintWrapper { }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } @@ -650,7 +650,7 @@ impl IKeyMintDevice for KeyMintWrapper { let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result = match result.rsp.unwrap() { @@ -734,7 +734,7 @@ impl IKeyMintDevice for KeyMintWrapper { }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result = match result.rsp.unwrap() { @@ -760,7 +760,7 @@ impl IKeyMintDevice for KeyMintWrapper { }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result = match result.rsp.unwrap() { @@ -772,7 +772,7 @@ impl IKeyMintDevice for KeyMintWrapper { let params: Result, rsbinder::Status> = kc.authorizations.iter().map(|p| { key_param_to_aidl(p.clone()) .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) - .map_err(|e| map_ks_error(e)) + .map_err(map_ks_error) }).collect(); let params = params?; @@ -798,7 +798,7 @@ impl IKeyMintDevice for KeyMintWrapper { let req = PerformOpReq::GetRootOfTrustChallenge(GetRootOfTrustChallengeRequest {}); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result = match result.rsp.unwrap() { @@ -821,7 +821,7 @@ impl IKeyMintDevice for KeyMintWrapper { let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Status::new_service_specific_error(result.error_code, None)); } let result = match result.rsp.unwrap() { @@ -873,14 +873,14 @@ impl IKeyMintDevice for KeyMintWrapper { impl KeyMintWrapper { pub fn new(security_level: SecurityLevel) -> Result { Ok(KeyMintWrapper { - security_level: security_level.clone(), + security_level, keymint: Mutex::new(init_keymint_ta(security_level)?), }) } pub fn reset_keymint_ta(&self) -> Result<()> { let mut keymint = self.keymint.lock().unwrap(); - *keymint = init_keymint_ta(self.security_level.clone())?; + *keymint = init_keymint_ta(self.security_level)?; Ok(()) } @@ -908,26 +908,22 @@ impl KeyMintWrapper { } else { None }; - let timestamp_token = if let Some(tt) = timestamp_token { - Some(kmr_wire::secureclock::TimeStampToken { + let timestamp_token = timestamp_token.map(|tt| kmr_wire::secureclock::TimeStampToken { challenge: tt.challenge, timestamp: kmr_wire::secureclock::Timestamp { milliseconds: tt.timestamp.milliSeconds, }, mac: tt.mac.clone(), - }) - } else { - None - }; + }); let req = PerformOpReq::OperationUpdateAad(UpdateAadRequest { op_handle, input: input.to_vec(), auth_token: hardware_auth_token, - timestamp_token: timestamp_token, + timestamp_token, }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Error::Binder( ExceptionCode::ServiceSpecific, result.error_code, @@ -957,26 +953,22 @@ impl KeyMintWrapper { } else { None }; - let timestamp_token = if let Some(tt) = timestamp_token { - Some(kmr_wire::secureclock::TimeStampToken { + let timestamp_token = timestamp_token.map(|tt| kmr_wire::secureclock::TimeStampToken { challenge: tt.challenge, timestamp: kmr_wire::secureclock::Timestamp { milliseconds: tt.timestamp.milliSeconds, }, mac: tt.mac.clone(), - }) - } else { - None - }; + }); let req = PerformOpReq::OperationUpdate(UpdateRequest { op_handle, input: input.to_vec(), auth_token: hardware_auth_token, - timestamp_token: timestamp_token, + timestamp_token, }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Error::Binder( ExceptionCode::ServiceSpecific, result.error_code, @@ -1008,31 +1000,27 @@ impl KeyMintWrapper { } else { None }; - let timestamp_token = if let Some(tt) = timestamp_token { - Some(kmr_wire::secureclock::TimeStampToken { + let timestamp_token = timestamp_token.map(|tt| kmr_wire::secureclock::TimeStampToken { challenge: tt.challenge, timestamp: kmr_wire::secureclock::Timestamp { milliseconds: tt.timestamp.milliSeconds, }, mac: tt.mac.clone(), - }) - } else { - None - }; + }); let input = input.map(|i| i.to_vec()); let signature = signature.map(|s| s.to_vec()); let confirmation_token = confirmation_token.map(|c| c.to_vec()); let req = PerformOpReq::OperationFinish(FinishRequest { op_handle, - input: input, - signature: signature, + input, + signature, auth_token: hardware_auth_token, - timestamp_token: timestamp_token, - confirmation_token: confirmation_token, + timestamp_token, + confirmation_token, }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Error::Binder( ExceptionCode::ServiceSpecific, result.error_code, @@ -1049,7 +1037,7 @@ impl KeyMintWrapper { pub fn op_abort(&self, op_handle: i64) -> Result<(), Error> { let req = PerformOpReq::OperationAbort(AbortRequest { op_handle }); let result = self.keymint.lock().unwrap().process_req(req); - if let None = result.rsp { + if result.rsp.is_none() { return Err(Error::Binder( ExceptionCode::ServiceSpecific, result.error_code, @@ -1155,8 +1143,8 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { }; let keys: Box = Box::new(soft::Keys::new( - config.crypto.root_kek_seed.clone(), - config.crypto.kak_seed.clone(), + config.crypto.root_kek_seed, + config.crypto.kak_seed, )); let rpc: Box = Box::new(soft::RpcArtifacts::new( soft::Derive::default(), diff --git a/src/keymaster/keymint_operation.rs b/src/keymaster/keymint_operation.rs index 20557e9..55fb5c6 100644 --- a/src/keymaster/keymint_operation.rs +++ b/src/keymaster/keymint_operation.rs @@ -49,7 +49,7 @@ impl IKeyMintOperation for KeyMintOperation { get_keymint_wrapper(self.security_level) .unwrap() .op_update_aad(self.op_handle, input, authToken, timeStampToken) - .map_err(|e| map_ks_error(e))?; + .map_err(map_ks_error)?; Ok(()) } @@ -64,8 +64,7 @@ impl IKeyMintOperation for KeyMintOperation { get_keymint_wrapper(self.security_level) .unwrap() .op_update(self.op_handle, input, authToken, timeStampToken) - .map_err(|e| map_ks_error(e)) - .and_then(|rsp: Vec| Ok(rsp.to_vec())) + .map_err(map_ks_error).map(|rsp: Vec| rsp.to_vec()) } fn r#finish( @@ -88,14 +87,13 @@ impl IKeyMintOperation for KeyMintOperation { timestampToken, confirmationToken, ) - .map_err(|e| map_ks_error(e)) - .and_then(|rsp: Vec| Ok(rsp.to_vec())) + .map_err(map_ks_error).map(|rsp: Vec| rsp.to_vec()) } fn r#abort(&self) -> rsbinder::status::Result<()> { get_keymint_wrapper(self.security_level) .unwrap() .op_abort(self.op_handle) - .map_err(|e| map_ks_error(e)) + .map_err(map_ks_error) } } diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 838c1e6..9c0bf7a 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -1005,9 +1005,9 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { forced: bool, ) -> Result { let _wp = self.watch("IKeystoreSecurityLevel::createOperation"); - Ok(self + self .create_operation(None, key, operation_parameters, forced) - .map_err(into_logged_binder)?) + .map_err(into_logged_binder) } fn generateKey( @@ -1097,9 +1097,9 @@ impl IOhMySecurityLevel for KeystoreSecurityLevel { forced: bool, ) -> Result { let _wp = self.watch("IOhMySecurityLevel::createOperation"); - Ok(self + self .create_operation(ctx, key, operation_parameters, forced) - .map_err(into_logged_binder)?) + .map_err(into_logged_binder) } fn generateKey( diff --git a/src/keymaster/super_key.rs b/src/keymaster/super_key.rs index b74b576..9f87207 100644 --- a/src/keymaster/super_key.rs +++ b/src/keymaster/super_key.rs @@ -364,7 +364,7 @@ impl SuperKeyManager { } pub fn level_accessible(&self, boot_level: i32) -> bool { - self.data.boot_level_key_cache.as_ref().map_or(false, |c| { + self.data.boot_level_key_cache.as_ref().is_some_and(|c| { c.lock().unwrap().level_accessible(boot_level as usize) }) } diff --git a/src/keymaster/utils.rs b/src/keymaster/utils.rs index 70243d9..0993490 100644 --- a/src/keymaster/utils.rs +++ b/src/keymaster/utils.rs @@ -20,7 +20,7 @@ use kmr_wire::{ }; pub fn log_params(params: &[KmKeyParameter]) -> Vec { - params.iter().cloned().collect::>() + params.to_vec() } /// Converts a set of key characteristics as returned from KeyMint into the internal @@ -623,7 +623,7 @@ pub fn key_creation_result_to_aidl( let params: Result, rsbinder::Status> = kc.authorizations.iter().map(|p| { key_param_to_aidl(p.clone()) .map_err(|_| Error::Km(ErrorCode::INVALID_ARGUMENT)) - .map_err(|e| map_ks_error(e)) + .map_err(map_ks_error) }).collect(); let params = params?; diff --git a/src/keymint/clock.rs b/src/keymint/clock.rs index 1d4af52..f9faccf 100644 --- a/src/keymint/clock.rs +++ b/src/keymint/clock.rs @@ -43,6 +43,6 @@ impl crypto::MonotonicClock for StdClock { warn!("failed to get time!"); return crypto::MillisecondsSinceEpoch(0); } - crypto::MillisecondsSinceEpoch(((time.tv_sec * 1000) + (time.tv_nsec / 1000 / 1000)).into()) + crypto::MillisecondsSinceEpoch((time.tv_sec * 1000) + (time.tv_nsec / 1000 / 1000)) } } diff --git a/src/main.rs b/src/main.rs index 2e636ed..29604ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,13 @@ use std::panic; -use lazy_static::lazy_static; use log::{debug, error, info}; use rsbinder::hub; use crate::{ android::system::keystore2::IKeystoreService::BnKeystoreService, config::{Backend, CONFIG}, - keymaster::{apex::ApexModuleInfo, service::KeystoreService}, + keymaster::service::KeystoreService, top::qwq2333::ohmykeymint::IOhMyKsService::BnOhMyKsService, }; diff --git a/src/plat/property_watcher.rs b/src/plat/property_watcher.rs index 657778a..7ebf2d5 100644 --- a/src/plat/property_watcher.rs +++ b/src/plat/property_watcher.rs @@ -33,7 +33,7 @@ impl PropertyWatcher { pub fn read(&self) -> Result { rsproperties::system_properties() - .get_with_result(&self.name.as_str()) + .get_with_result(self.name.as_str()) .context(anyhow!("Property '{}' not found", self.name)) } @@ -42,7 +42,7 @@ impl PropertyWatcher { F: FnMut(&str) -> Result, { rsproperties::system_properties() - .get_with_result(&self.name.as_str()) + .get_with_result(self.name.as_str()) .context(anyhow!("Property '{}' not found", self.name)) .and_then(|value| f(value.as_str())) } diff --git a/src/plat/utils.rs b/src/plat/utils.rs index fa80a8f..bb0c375 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -109,11 +109,11 @@ pub fn get_aaid(uid: u32) -> anyhow::Result> { // unsafe { // libc::seteuid(1017); // KEYSTORE_UID // } - let result = pm.getKeyAttestationApplicationId(uid as i32); + // unsafe { // libc::seteuid(current_euid); // } - result + pm.getKeyAttestationApplicationId(uid as i32) }; if let Result::Ok(application_id) = result { break application_id; @@ -153,7 +153,7 @@ fn encode_application_id( .hash(sig.data.as_slice()) .map_err(|e| anyhow::anyhow!("Failed to hash signature: {:?}", e))?; - let octet_string = x509_cert::der::asn1::OctetString::new(&result)?; + let octet_string = x509_cert::der::asn1::OctetString::new(result)?; let result = signature_digests.insert_ordered(octet_string).map_err(|e| { anyhow::anyhow!(err!("Failed to encode AttestationApplicationId: {:?}", e)) @@ -177,7 +177,7 @@ fn encode_application_id( let result = super::aaid::AttestationApplicationId { package_info_records: package_info_set, - signature_digests: signature_digests, + signature_digests, }; result diff --git a/src/utils.rs b/src/utils.rs index f277ca7..96c7abf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,7 +9,7 @@ pub fn get_current_time_in_milliseconds() -> i64 { // SAFETY: The pointer is valid because it comes from a reference, and clock_gettime doesn't // retain it beyond the call. unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut current_time) }; - current_time.tv_sec as i64 * 1000 + (current_time.tv_nsec as i64 / 1_000_000) + current_time.tv_sec * 1000 + (current_time.tv_nsec / 1_000_000) } pub trait ParcelExt { From 7cd340b809d9ac3a6c517d9e2e2fe78f19d84eeb Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 14 Nov 2025 23:15:07 +0800 Subject: [PATCH 41/46] fix: wrong vendor patch level format Signed-off-by: qwq233 --- src/keymaster/keymint_device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index 51b89fd..abf1afb 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -1201,7 +1201,7 @@ fn init_keymint_ta(security_level: SecurityLevel) -> Result { let req = PerformOpReq::SetHalInfo(kmr_wire::SetHalInfoRequest { os_version: config.trust.os_version as u32, os_patchlevel, - vendor_patchlevel: os_patchlevel, + vendor_patchlevel: boot_patchlevel, }); let resp = ta.process_req(req); if resp.error_code != 0 { From e5ac4d695958d7f136ee5580ce4259976098b261 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Fri, 14 Nov 2025 23:52:06 +0800 Subject: [PATCH 42/46] chore: release v0.1.2 Signed-off-by: qwq233 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4626548..c0e51f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "OhMyKeymint" -version = "0.1.1" +version = "0.1.2" edition = "2021" license = "AGPL-3.0-or-later" From 838c19eaf3a394d2951ae78fef3811e662fe29cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:20:07 +0800 Subject: [PATCH 43/46] chore(deps): update cddl-cat requirement from ^0.6.1 to ^0.7.0 (#7) Updates the requirements on [cddl-cat](https://github.com/ericseppanen/cddl-cat) to permit the latest version. - [Commits](https://github.com/ericseppanen/cddl-cat/compare/v0.6.1...v0.7.0) --- updated-dependencies: - dependency-name: cddl-cat dependency-version: 0.7.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- common/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index e4c3d9f..71175e1 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" license = "Apache-2.0" [dependencies] -cddl-cat = { version = "^0.6.1", optional = true } +cddl-cat = { version = "^0.7.0", optional = true } ciborium = { version = "^0.2.0", default-features = false } ciborium-io = "^0.2.0" coset = "0.4.0" From b69016cf431afbe0f28b79b708f36e409332b9a4 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Wed, 7 Jan 2026 13:28:27 +0800 Subject: [PATCH 44/46] chore: bump dependencies version Signed-off-by: qwq233 --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0e51f6..0d5a00f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,17 +16,17 @@ kmr-common = { path = "common" } kmr-crypto-boring = { path = "boringssl" } kmr-ta = { path = "ta" } kmr-wire = { path = "wire" } -libc = "0.2.176" +libc = "0.2.179" prost = "0.14.1" prost-types = "0.14.1" hex = "0.4.3" log4rs = "1.4.0" -log = "0.4.28" -rsbinder = { version = "0.4.2", features = ["android_12_plus"] } -rusqlite = { version = "0.37.0", features = ["bundled"] } +log = "0.4.29" +rsbinder = { version = "0.5.0", features = ["android_12_plus"] } +rusqlite = { version = "0.38.0", features = ["bundled"] } anyhow = "1.0.100" async-trait = "0.1.89" -tokio = { version = "1.47.1", features = ["macros", "libc"] } +tokio = { version = "1.49.0", features = ["macros", "libc"] } lazy_static = "1.5.0" thiserror = "2.0.17" serde = "1.0.228" @@ -34,7 +34,7 @@ x509-cert = "0.2.5" watchdog_rs = "*" nix = { version = "0.30.1", features = ["mman"] } rsproperties = { version = "0.2.2", features = ["builder"] } -libsqlite3-sys = "0.35.0" +libsqlite3-sys = "0.36.0" rand = "0.9.2" der = "0.7.10" toml = "0.9.8" @@ -81,4 +81,4 @@ rsproperties-service = { path = "rsproperties-service" } [build-dependencies] prost-build = "0.14.1" -rsbinder-aidl = "0.4.3" +rsbinder-aidl = "0.5.0" From 18ef07587d97bbd7ae2c8c5774cbbb1bca4dd949 Mon Sep 17 00:00:00 2001 From: qwq233 Date: Sat, 10 Jan 2026 23:13:29 +0800 Subject: [PATCH 45/46] feat: init base for injector Signed-off-by: qwq233 --- Cargo.toml | 7 +- boringssl/src/km.rs | 7 +- common/src/crypto/hmac.rs | 2 +- common/src/tag.rs | 6 +- injector/Cargo.toml | 21 ++ injector/src/logging.rs | 45 +++ injector/src/main.rs | 388 ++++++++++++++++++++++ injector/src/sys.rs | 508 +++++++++++++++++++++++++++++ injector/src/utils.rs | 192 +++++++++++ src/keymaster/db.rs | 4 +- src/keymaster/keymint_device.rs | 48 +-- src/keymaster/keymint_operation.rs | 6 +- src/keymaster/security_level.rs | 36 +- src/keymaster/super_key.rs | 7 +- src/plat/property_watcher.rs | 3 - src/plat/utils.rs | 2 +- ta/src/cert.rs | 2 +- ta/src/clock.rs | 1 - ta/src/keys.rs | 5 +- ta/src/lib.rs | 2 +- 20 files changed, 1224 insertions(+), 68 deletions(-) create mode 100644 injector/Cargo.toml create mode 100644 injector/src/logging.rs create mode 100644 injector/src/main.rs create mode 100644 injector/src/sys.rs create mode 100644 injector/src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 0d5a00f..99ff138 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ kmr-common = { path = "common" } kmr-crypto-boring = { path = "boringssl" } kmr-ta = { path = "ta" } kmr-wire = { path = "wire" } -libc = "0.2.179" +libc = "0.2.180" prost = "0.14.1" prost-types = "0.14.1" hex = "0.4.3" @@ -48,6 +48,8 @@ rsproperties-service = "*" android_logger = "0.15.1" multi_log = "0.1.2" +[profile.dev] +incremental = true [profile.release] opt-level = 'z' # Optimize for size @@ -56,6 +58,7 @@ codegen-units = 1 # Reduce number of codegen units to increase optimizations panic = 'abort' # Abort on panic strip = true # Strip symbols from binary* debuginfo = 0 # Disable debug info +incremental = true [workspace] members = [ @@ -67,6 +70,7 @@ members = [ "wire", "watchdog", "rsproperties-service", + "injector", ] [patch.crates-io] @@ -77,6 +81,7 @@ kmr-ta = { path = "ta" } kmr-tests = { path = "tests" } kmr-wire = { path = "wire" } watchdog_rs = { path = "watchdog" } +injector = { path = "injector" } rsproperties-service = { path = "rsproperties-service" } [build-dependencies] diff --git a/boringssl/src/km.rs b/boringssl/src/km.rs index 8ad73de..a5f180d 100644 --- a/boringssl/src/km.rs +++ b/boringssl/src/km.rs @@ -62,7 +62,7 @@ pub fn generate_random_data(size: usize) -> Result, Error> { #[allow(non_snake_case)] fn randomBytes(buf: *mut u8, len: usize) -> bool { - let mut rng = crate::rng::BoringRng::default(); + let mut rng = crate::rng::BoringRng; rng.fill_bytes(unsafe { std::slice::from_raw_parts_mut(buf, len) }); true } @@ -370,10 +370,7 @@ pub fn hkdf_expand(out_len: usize, prk: &[u8], info: &[u8]) -> Result bool { let digest = MessageDigest::sha256(); - match openssl::pkcs5::pbkdf2_hmac(prk, info, 1, digest, out_key) { - Ok(_) => true, - Err(_) => false, - } + openssl::pkcs5::pbkdf2_hmac(prk, info, 1, digest, out_key).is_ok() } /// A wrapper around the boringssl EC_KEY type that frees it on drop. diff --git a/common/src/crypto/hmac.rs b/common/src/crypto/hmac.rs index 699f5a9..7ef4b9c 100644 --- a/common/src/crypto/hmac.rs +++ b/common/src/crypto/hmac.rs @@ -32,7 +32,7 @@ pub const MAX_KEY_SIZE_BITS: usize = 1024; pub struct Key(pub Vec); fn valid_size(key_size: KeySizeInBits, max_size_bits: usize) -> Result<(), Error> { - if key_size.0 % 8 != 0 { + if !key_size.0.is_multiple_of(8) { Err(km_err!( UnsupportedKeySize, "key size {} bits not a multiple of 8", diff --git a/common/src/tag.rs b/common/src/tag.rs index 87b5fa7..471a573 100644 --- a/common/src/tag.rs +++ b/common/src/tag.rs @@ -200,7 +200,7 @@ pub fn transcribe_tags( for param in src { let tag = param.tag(); dup_checker.add(tag)?; - if tags.iter().any(|t| *t == tag) { + if tags.contains(&tag) { dest.try_push(param.clone())?; } } @@ -807,9 +807,7 @@ fn check_aes_import_params( /// Check the parameter validity for an AES key that is about to be generated or imported. fn check_aes_params(params: &[KeyParam]) -> Result<(), Error> { - let gcm_support = params - .iter() - .any(|p| *p == KeyParam::BlockMode(BlockMode::Gcm)); + let gcm_support = params.contains(&KeyParam::BlockMode(BlockMode::Gcm)); if gcm_support { let min_mac_len = get_tag_value!(params, MinMacLength, ErrorCode::MissingMinMacLength)?; if (min_mac_len % 8 != 0) || !(96..=128).contains(&min_mac_len) { diff --git a/injector/Cargo.toml b/injector/Cargo.toml new file mode 100644 index 0000000..a2ccaec --- /dev/null +++ b/injector/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "injector" +version = "0.1.0" +edition = "2021" +license = "AGPL-3.0-or-later" + +[[bin]] +name = "inject" +path = "src/main.rs" + +[dependencies] +libc = "0.2.177" +log = "0.4.28" +log4rs = "1.4.0" +nix = { version = "0.30.1", features = ["dir", "fs", "ptrace", "signal"] } +lsplt-rs = "2.1.5" +anyhow = "1.0.100" + +[target.'cfg(target_os = "android")'.dependencies] +android_logger = "0.15.1" +multi_log = "0.1.2" diff --git a/injector/src/logging.rs b/injector/src/logging.rs new file mode 100644 index 0000000..0caf184 --- /dev/null +++ b/injector/src/logging.rs @@ -0,0 +1,45 @@ +use log::LevelFilter; +use log4rs::append::console::ConsoleAppender; +use log4rs::config::{Appender, Config, Root}; +use log4rs::encode::pattern::PatternEncoder; + +const PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S %Z)(utc)} [{h({l})}] {M} - {m}{n}"; + +#[cfg(not(target_os = "android"))] +pub fn init_logger() { + let stdout = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new(PATTERN))) + .build(); + let root = Root::builder().appender("stdout").build(LevelFilter::Debug); + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .build(root) + .unwrap(); + log4rs::init_config(config).unwrap(); +} + +#[cfg(target_os = "android")] +pub fn init_logger() { + let config = android_logger::Config::default() + .with_max_level(LevelFilter::Debug) + .with_tag("OhMyKeymint"); + + let android_logger = android_logger::AndroidLogger::new(config); + + let stdout = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new(PATTERN))) + .build(); + let root = Root::builder().appender("stdout").build(LevelFilter::Debug); + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .build(root) + .unwrap(); + + let log4rs = log4rs::Logger::new(config); + + multi_log::MultiLogger::init( + vec![Box::new(android_logger), Box::new(log4rs)], + log::Level::Debug, + ) + .unwrap(); +} diff --git a/injector/src/main.rs b/injector/src/main.rs new file mode 100644 index 0000000..98aa067 --- /dev/null +++ b/injector/src/main.rs @@ -0,0 +1,388 @@ +use std::ffi::c_void; + +use anyhow::{bail, Context, Result}; +use log::{debug, error}; +use nix::{sys::signal::Signal, unistd::Pid}; + +use crate::sys::wait_pid; + +pub mod logging; +pub mod sys; +pub mod utils; + +fn main() { + logging::init_logger(); + let (pid, _) = utils::find_process_by_name("keystore2").unwrap(); + let pid = Pid::from_raw(pid); + inject_library(pid, entry).unwrap(); +} + +fn inject_library(pid: Pid, entry: extern "C" fn(*const c_void) -> bool) -> Result<()> { + let self_path = + std::fs::read_link("/proc/self/exe").context("Failed to read link /proc/self/exe")?; + + nix::sys::ptrace::attach(pid)?; + debug!("Attached to process {}", pid); + + if let Err(e) = wait_pid(pid, Signal::SIGSTOP) { + bail!("Failed to wait for process {} to stop: {}", pid, e); + } + + let mut regs = sys::get_regs(pid)?; + let backup_regs = regs; + + let local_maps = lsplt_rs::MapInfo::scan("self"); + let remote_maps = lsplt_rs::MapInfo::scan(pid.as_raw().to_string().as_str()); + + // Helper closure to resolve function address + let resolve = |lib: &str, name: &str| -> Result { + utils::resolve_func_addr(&local_maps, &remote_maps, lib, name) + .or_else(|_| utils::resolve_func_addr(&local_maps, &remote_maps, "libc.so", name)) + // Fallback to libc for newer android + }; + + // Helper to push data to remote stack and update regs SP + let mut push_to_remote_stack = |data: &[u8]| -> Result { + let sp = { + #[cfg(target_arch = "x86_64")] + { + regs.rsp as usize + } + #[cfg(target_arch = "x86")] + { + regs.esp as usize + } + #[cfg(target_arch = "aarch64")] + { + regs.sp as usize + } + #[cfg(target_arch = "arm")] + { + regs.uregs[13] as usize + } + }; + // sys::push_stack returns the NEW stack pointer address + let new_sp = sys::push_stack(pid, sp, data, false)?; + + // Update local regs copy + #[cfg(target_arch = "x86_64")] + { + regs.rsp = new_sp as u64; + } + #[cfg(target_arch = "x86")] + { + regs.esp = new_sp as u32; + } + #[cfg(target_arch = "aarch64")] + { + regs.sp = new_sp as u64; + } + #[cfg(target_arch = "arm")] + { + regs.uregs[13] = new_sp as u32; + } + + // Commit SP change to remote process so subsequent remote_call works correctly + sys::set_regs(pid, ®s)?; + Ok(new_sp) + }; + + let libc_return_addr = utils::resolve_return_addr(&remote_maps, "libc.so")?; + debug!("Resolved libc return address: 0x{:x}", libc_return_addr); + + let close_addr = resolve("libc.so", "close")?; + let socket_addr = resolve("libc.so", "socket")?; + let bind_addr = resolve("libc.so", "bind")?; + let recvmsg_addr = resolve("libc.so", "recvmsg")?; + let errno_addr = resolve("libc.so", "__errno").ok(); + let strlen_addr = resolve("libc.so", "strlen").ok(); + + let dlopen_addr = resolve("libdl.so", "dlopen")?; + let dlerror_addr = resolve("libdl.so", "dlerror").ok(); + + let get_remote_errno = || -> Result { + if let Some(addr) = errno_addr { + let ptr = sys::remote_call(pid, addr, libc_return_addr, &[])?; + let mut buf = [0u8; 4]; + sys::read_stack(pid, ptr, &mut buf)?; + Ok(i32::from_ne_bytes(buf)) + } else { + Ok(0) + } + }; + + let close_remote = |fd: i32| -> Result<()> { + let args = vec![fd as usize]; + if sys::remote_call(pid, close_addr, libc_return_addr, &args)? != 0 { + error!("Remote close failed for fd {}", fd); + } + Ok(()) + }; + + // Prepare FD Passing + utils::set_sockcreate_con("u:object_r:system_file:s0")?; + + // Create local socket + let local_socket = + unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 0) }; + if local_socket == -1 { + bail!( + "Failed to create local socket: {}", + std::io::Error::last_os_error() + ); + } + // Ensure local socket is closed when we drop/exit + let _local_sock_guard = { + // Simple scope guard to close fd + struct FdGuard(i32); + impl Drop for FdGuard { + fn drop(&mut self) { + unsafe { + libc::close(self.0); + } + } + } + FdGuard(local_socket) + }; + // Set SELinux context for the file + utils::set_file_con(&self_path, "u:object_r:system_file:s0")?; + + let local_lib_file = std::fs::OpenOptions::new() + .read(true) + .open(&self_path) + .context("Failed to open self executable")?; + use std::os::unix::io::AsRawFd; + let local_lib_fd = local_lib_file.as_raw_fd(); + + let args = vec![ + libc::AF_UNIX as usize, + (libc::SOCK_DGRAM | libc::SOCK_CLOEXEC) as usize, + 0, + ]; + let remote_fd = sys::remote_call(pid, socket_addr, libc_return_addr, &args)? as i32; + if remote_fd == -1 { + let err = get_remote_errno()?; + bail!("Failed to create remote socket. Remote errno: {}", err); + } + + // generate magic socket name + let mut magic_bytes = Vec::with_capacity(16); + let time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH)? + .subsec_nanos(); + for i in 0..16 { + magic_bytes.push(b'a' + ((time.wrapping_add(i) % 26) as u8)); // a-z + } + + // Construct sockaddr_un + // Layout: family (u16) + path (108 bytes) + // Using explicit bytes to ensure layout matches C struct exactly without imports + let mut addr_bytes = vec![0u8; std::mem::size_of::()]; + // Set family AF_UNIX + let family = (libc::AF_UNIX as u16).to_ne_bytes(); + addr_bytes[0] = family[0]; + addr_bytes[1] = family[1]; + // Set abstract path (starts with \0, then magic) + // sun_path offset is 2 + addr_bytes[2] = 0; + for (i, b) in magic_bytes.iter().enumerate() { + if 3 + i < addr_bytes.len() { + addr_bytes[3 + i] = *b; + } + } + let addr_len = 2 + 1 + magic_bytes.len(); // family + null + magic + + debug!( + "Generated magic socket: @{}", + String::from_utf8_lossy(&magic_bytes) + ); + + let remote_addr_ptr = push_to_remote_stack(&addr_bytes)?; + + let args = vec![remote_fd as usize, remote_addr_ptr, addr_len]; + let bind_res = sys::remote_call(pid, bind_addr, libc_return_addr, &args)?; + if (bind_res as isize) == -1 { + let err = get_remote_errno()?; + close_remote(remote_fd)?; + bail!("Failed to bind remote socket. Remote errno: {}", err); + } + + // CMSG buffer + let cmsg_space = + unsafe { libc::CMSG_SPACE(std::mem::size_of::() as u32) as usize }; + let cmsg_buf = vec![0u8; cmsg_space]; + let remote_cmsg_ptr = push_to_remote_stack(&cmsg_buf)?; + + let mut msg: libc::msghdr = unsafe { std::mem::zeroed() }; + msg.msg_control = remote_cmsg_ptr as *mut c_void; + msg.msg_controllen = cmsg_space; + + let msg_bytes = unsafe { + std::slice::from_raw_parts( + &msg as *const _ as *const u8, + std::mem::size_of::(), + ) + }; + let remote_msg_ptr = push_to_remote_stack(msg_bytes)?; + + // 6b. Sendmsg (Local) -> Send the FD + // Construct local address to send TO + let mut local_dest_addr: libc::sockaddr_un = unsafe { std::mem::zeroed() }; + local_dest_addr.sun_family = libc::AF_UNIX as u16; + local_dest_addr.sun_path[0] = 0; // Abstract + for (i, b) in magic_bytes.iter().enumerate() { + local_dest_addr.sun_path[1 + i] = *b; + } + + // Construct Control Message + // Requires explicit CMSG construction + let mut local_cmsg_buf = vec![0u8; cmsg_space]; + let mut local_iov = libc::iovec { + iov_base: std::ptr::null_mut(), + iov_len: 0, + }; // Send 0 bytes of real data + + let mut local_hdr: libc::msghdr = unsafe { std::mem::zeroed() }; + local_hdr.msg_name = &mut local_dest_addr as *mut _ as *mut c_void; + local_hdr.msg_namelen = addr_len as u32; + local_hdr.msg_iov = &mut local_iov; + local_hdr.msg_iovlen = 1; + local_hdr.msg_control = local_cmsg_buf.as_mut_ptr() as *mut c_void; + local_hdr.msg_controllen = cmsg_space; + + unsafe { + let cmsg = libc::CMSG_FIRSTHDR(&local_hdr); + (*cmsg).cmsg_level = libc::SOL_SOCKET; + (*cmsg).cmsg_type = libc::SCM_RIGHTS; + (*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::() as u32) as usize; + *(libc::CMSG_DATA(cmsg) as *mut libc::c_int) = local_lib_fd; + // Update controllen to actual length used + local_hdr.msg_controllen = (*cmsg).cmsg_len; + } + + let send_res = unsafe { libc::sendmsg(local_socket, &local_hdr, 0) }; + if send_res == -1 { + close_remote(remote_fd)?; + bail!( + "Failed to send FD locally: {}", + std::io::Error::last_os_error() + ); + } + debug!("Sent FD {} to remote abstract socket", local_lib_fd); + + let args = vec![ + remote_fd as usize, + remote_msg_ptr, + libc::MSG_WAITALL as usize, + ]; + let recv_res = sys::remote_call(pid, recvmsg_addr, libc_return_addr, &args)? as isize; + + if recv_res == -1 { + let err = get_remote_errno()?; + close_remote(remote_fd)?; + bail!("Remote recvmsg failed. Errno: {}", err); + } + + // Retrieve Received FD from Remote Memory + let mut remote_cmsg_data = vec![0u8; cmsg_space]; + sys::read_stack(pid, remote_cmsg_ptr, &mut remote_cmsg_data)?; + + let cmsg_hdr_len = unsafe { libc::CMSG_LEN(0) } as usize; + let remote_lib_fd_bytes = &remote_cmsg_data[cmsg_hdr_len..cmsg_hdr_len + 4]; + let remote_lib_fd = i32::from_ne_bytes(remote_lib_fd_bytes.try_into().unwrap()); + + debug!("Remote received FD: {}", remote_lib_fd); + + close_remote(remote_fd)?; + + let info_size = 64; // Safe upper bound + let mut info_bytes = vec![0u8; info_size]; + + let flags: u64 = 0x10; + info_bytes[0..8].copy_from_slice(&flags.to_ne_bytes()); + + // library_fd offset + // 64-bit: 0(u64), 8(ptr), 16(u64), 24(int), 28(int library_fd) + // 32-bit: 0(u64), 8(ptr), 12(u32), 16(int), 20(int library_fd) + let fd_offset = if std::mem::size_of::() == 8 { + 28 + } else { + 20 + }; + info_bytes[fd_offset..fd_offset + 4].copy_from_slice(&remote_lib_fd.to_ne_bytes()); + + let remote_info_ptr = push_to_remote_stack(&info_bytes)?; + + // Push library path string + let lib_path_str = self_path.to_string_lossy(); + let lib_path_c = std::ffi::CString::new(lib_path_str.as_bytes())?; + let remote_path_ptr = push_to_remote_stack(lib_path_c.as_bytes_with_nul())?; + + // Call dlopen + // args: filename, flags (RTLD_NOW=2), extinfo + let args = vec![remote_path_ptr, libc::RTLD_NOW as usize, remote_info_ptr]; + let handle = sys::remote_call(pid, dlopen_addr, libc_return_addr, &args)?; + + debug!("Remote dlopen handle: 0x{:x}", handle); + + if handle == 0 { + // Read dlerror + if let (Some(err_fn), Some(str_fn)) = (dlerror_addr, strlen_addr) { + let err_ptr = sys::remote_call(pid, err_fn, libc_return_addr, &[])?; + if err_ptr != 0 { + let len = sys::remote_call(pid, str_fn, libc_return_addr, &[err_ptr])?; + if len > 0 && len < 1024 { + let mut err_buf = vec![0u8; len]; + sys::read_stack(pid, err_ptr, &mut err_buf)?; + error!("dlopen failed: {}", String::from_utf8_lossy(&err_buf)); + } + } + } + // Close the leaked lib_fd in remote + close_remote(remote_lib_fd)?; + bail!("Remote dlopen failed"); + } + + close_remote(remote_lib_fd)?; + + // a + let local_entry_addr = entry as usize; + let local_base = utils::resolve_base_addr( + &local_maps, + &self_path.file_name().unwrap().to_string_lossy(), + )?; + let offset = local_entry_addr - local_base; + debug!( + "Local Entry: 0x{:x}, Local Base: 0x{:x}, Offset: 0x{:x}", + local_entry_addr, local_base, offset + ); + + let remote_maps = lsplt_rs::MapInfo::scan(pid.as_raw().to_string().as_str()); // Refresh remote maps + let remote_base = utils::resolve_base_addr( + &remote_maps, + &self_path.file_name().unwrap().to_string_lossy(), + )?; + + let injector_entry = remote_base + offset; + debug!("Found 'entry' at: 0x{:x}", injector_entry); + + if injector_entry == 0 { + bail!("Failed to find 'entry' symbol in injected library"); + } + + let args = vec![handle]; + sys::remote_call(pid, injector_entry, libc_return_addr, &args)?; + + // Cleanup + debug!("Restore context and detach"); + sys::set_regs(pid, &backup_regs)?; + nix::sys::ptrace::detach(pid, None)?; + + Ok(()) +} + +#[no_mangle] +#[allow(unused)] +pub extern "C" fn entry(handle: *const c_void) -> bool { + true +} diff --git a/injector/src/sys.rs b/injector/src/sys.rs new file mode 100644 index 0000000..af14ce1 --- /dev/null +++ b/injector/src/sys.rs @@ -0,0 +1,508 @@ +use std::os::unix::fs::FileExt as _; +use std::path::PathBuf; + +use std::ffi::c_void; + +use anyhow::{anyhow, bail, Context, Result}; +use libc::iovec; +use log::{debug, error, trace}; +use nix::sys::signal::Signal; +use nix::{ + sys::wait::{WaitPidFlag, WaitStatus}, + unistd::Pid, +}; + +#[cfg(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "x86_64", + target_arch = "x86" +))] +pub type Regs = libc::user_regs_struct; + +pub const NT_PRSTATUS: std::ffi::c_int = 1; + +pub fn align_stack(regs: &mut Regs, preserve: usize) { + #[cfg(target_arch = "x86_64")] + { + regs.rsp = (regs.rsp.wrapping_sub(preserve as u64)) & !0xf; + } + #[cfg(target_arch = "x86")] + { + regs.esp = (regs.esp.wrapping_sub(preserve as u32)) & !0xf; + } + #[cfg(target_arch = "aarch64")] + { + regs.sp = (regs.sp.wrapping_sub(preserve as u64)) & !0xf; + } + #[cfg(target_arch = "arm")] + { + regs.uregs[13] = (regs.uregs[13].wrapping_sub(preserve as u32)) & !0xf; + } +} + +pub fn wait_pid(pid: Pid, target: Signal) -> Result<()> { + loop { + match nix::sys::wait::waitpid(pid, Some(WaitPidFlag::empty()))? { + WaitStatus::Stopped(_, sig) => { + if sig == target { + return Ok(()); + } + if sig == Signal::SIGTRAP { + continue; + } + bail!("Process {} stopped with signal {}", pid, sig.as_str()); + } + WaitStatus::Signaled(_, sig, _) => { + bail!("Process {} terminated with signal {}", pid, sig.as_str()); + } + WaitStatus::Exited(_, code) => { + bail!("Process {} exited with code {}", pid, code); + } + _ => continue, + } + } +} + +pub fn get_regs(pid: Pid) -> Result { + let mut regs: Regs = unsafe { std::mem::zeroed() }; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + let result = unsafe { + libc::ptrace( + libc::PTRACE_GETREGS, + pid.as_raw(), + 0, + &mut regs as *mut _ as *mut c_void, + ) + }; + + if result == -1 { + bail!( + "ptrace(PTRACE_GETREGS) failed: {}", + std::io::Error::last_os_error() + ); + } + } + + #[cfg(any(target_arch = "aarch64", target_arch = "arm"))] + { + let mut iov = libc::iovec { + iov_base: &mut regs as *mut _ as *mut c_void, + iov_len: std::mem::size_of::(), + }; + + let result = unsafe { + libc::ptrace( + libc::PTRACE_GETREGSET, + pid.as_raw(), + NT_PRSTATUS as *mut c_void, + &mut iov as *mut _ as *mut c_void, + ) + }; + + if result == -1 { + bail!( + "ptrace(PTRACE_GETREGS) failed: {}", + std::io::Error::last_os_error() + ); + } + } + + Ok(regs) +} + +pub fn set_regs(pid: Pid, regs: &Regs) -> Result<()> { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + let result = unsafe { + libc::ptrace( + libc::PTRACE_SETREGS, + pid.as_raw(), + 0, + regs as *const _ as *mut c_void, + ) + }; + + if result == -1 { + bail!( + "ptrace(PTRACE_SETREGS) failed: {}", + std::io::Error::last_os_error() + ); + } + } + + #[cfg(any(target_arch = "aarch64", target_arch = "arm"))] + { + let mut iov = libc::iovec { + iov_base: regs as *const _ as *mut c_void, + iov_len: std::mem::size_of::(), + }; + + let result = unsafe { + libc::ptrace( + libc::PTRACE_SETREGSET, + pid.as_raw(), + NT_PRSTATUS as *mut c_void, + &mut iov as *mut _ as *mut c_void, + ) + }; + + if result == -1 { + bail!( + "ptrace(PTRACE_SETREGS) failed: {}", + std::io::Error::last_os_error() + ); + } + } + + Ok(()) +} + +pub fn read_stack(pid: Pid, remote_addr: usize, buf: &mut [u8]) -> Result { + let local = iovec { + iov_base: buf.as_mut_ptr() as *mut c_void, + iov_len: buf.len(), + }; + + let remote = iovec { + iov_base: remote_addr as *mut c_void, + iov_len: buf.len(), + }; + + let result = unsafe { + libc::process_vm_readv( + pid.as_raw(), + &local as *const iovec, + 1, + &remote as *const iovec, + 1, + 0, + ) + }; + + if result == -1 { + bail!( + "process_vm_readv failed: {}", + std::io::Error::last_os_error() + ); + } else if result != buf.len() as isize { + bail!( + "process_vm_readv read incomplete data: {}/{} bytes", + result, + buf.len() + ) + } else { + Ok(result as usize) + } +} + +pub fn push_stack(pid: Pid, remote_addr: usize, data: &[u8], use_proc_mem: bool) -> Result { + let new_addr = remote_addr + .checked_sub(data.len()) + .ok_or_else(|| anyhow!("Stack overflow when pushing data"))?; + + if use_proc_mem { + let mem_path = PathBuf::from(format!("/proc/{}/mem", pid.as_raw())); + let mem_file = std::fs::OpenOptions::new() + .write(true) + .open(&mem_path) + .context(format!("Failed to open {}", mem_path.display()))?; + + mem_file + .write_at(data, new_addr as u64) + .context(format!("Failed to write to address 0x{:x}", new_addr))?; + + Ok(new_addr) + } else { + let local = iovec { + iov_base: data.as_ptr() as *mut c_void, + iov_len: data.len(), + }; + + let remote = iovec { + iov_base: new_addr as *mut c_void, + iov_len: data.len(), + }; + + let result = unsafe { + libc::process_vm_writev( + pid.as_raw(), + &local as *const iovec, + 1, + &remote as *const iovec, + 1, + 0, + ) + }; + + if result == -1 { + error!( + "process_vm_writev failed: {}", + std::io::Error::last_os_error() + ); + Err(anyhow!( + "process_vm_writev failed: {}", + std::io::Error::last_os_error() + )) + } else if result != data.len() as isize { + Err(anyhow!( + "process_vm_writev wrote incomplete data: {}/{} bytes", + result, + data.len() + )) + } else { + Ok(new_addr) + } + } +} + +pub fn setup_remote_call( + pid: Pid, + regs: &mut Regs, + func_addr: usize, + return_addr: usize, + args: &[usize], +) -> Result<()> { + align_stack(regs, 0); + trace!( + "Setting up remote call: func_addr=0x{:x}, return_addr=0x{:x}, regs=0{:?}, args={:?}", + func_addr, + return_addr, + regs, + args + ); + + #[cfg(target_arch = "x86_64")] + { + let mut sp = regs.rsp as usize; + + // set up arguments in registers + if args.len() > 0 { + regs.rdi = args[0] as u64; + } + if args.len() > 1 { + regs.rsi = args[1] as u64; + } + if args.len() > 2 { + regs.rdx = args[2] as u64; + } + if args.len() > 3 { + regs.rcx = args[3] as u64; + } + if args.len() > 4 { + regs.r8 = args[4] as u64; + } + if args.len() > 5 { + regs.r9 = args[5] as u64; + } + + if args.len() > 6 { + for i in (6..args.len()).rev() { + let arg_bytes = args[i].to_ne_bytes(); + sp = push_stack(pid, sp, &arg_bytes, false)?; + } + } + + let ret_bytes = return_addr.to_ne_bytes(); + sp = push_stack(pid, sp, &ret_bytes, false)?; + regs.rsp = sp as u64; + regs.rip = func_addr as u64; + } + + #[cfg(target_arch = "x86")] + { + let mut sp = regs.esp as usize; + + for i in (0..args.len()).rev() { + let arg_bytes = (args[i] as u32).to_ne_bytes(); + sp = push_stack(pid, sp, &arg_bytes, false)?; + } + + let ret_bytes = (return_addr as u32).to_ne_bytes(); + sp = push_stack(pid, sp, &ret_bytes, false)?; + + regs.esp = sp as u32; + regs.eip = func_addr as u32; + } + + #[cfg(target_arch = "aarch64")] + { + const ARG_REG_COUNT: usize = 8; + let mut sp = regs.sp as usize; + + // set up arguments in registers + for i in 0..args.len().min(ARG_REG_COUNT) { + regs.regs[i] = args[i] as u64; + } + // jump to stack for additional arguments + if args.len() > ARG_REG_COUNT { + // ensure 16-byte alignment + let size = (args.len() - ARG_REG_COUNT) * std::mem::size_of::(); + let target_sp = sp.wrapping_sub(size) & !0xf; + sp = target_sp + size; + + for i in (ARG_REG_COUNT..args.len()).rev() { + sp = push_stack(pid, sp, &args[i].to_ne_bytes(), false)?; + } + } + // set link register to dummy return address + regs.regs[30] = return_addr as u64; + // set program counter to function address + regs.pc = func_addr as u64; + // ensure proper instruction set state + regs.sp = sp as u64; + } + + #[cfg(target_arch = "arm")] + { + const REG_ARGS_COUNT: usize = 4; + let mut sp = regs.uregs[13] as usize; // SP + + if args.len() > REG_ARGS_COUNT { + let stack_args_len = (args.len() - REG_ARGS_COUNT) * 4; + let target_sp = (sp - stack_args_len) & !0x7; + sp = target_sp + stack_args_len; + + for i in (REG_ARGS_COUNT..args.len()).rev() { + let arg_bytes = (args[i] as u32).to_ne_bytes(); + sp = push_stack(pid, sp, &arg_bytes, false)?; + } + } + + for i in 0..std::cmp::min(args.len(), REG_ARGS_COUNT) { + regs.uregs[i] = args[i] as u32; + } + + regs.uregs[14] = return_addr as u32; // LR + regs.uregs[13] = sp as u32; // SP + + if (func_addr & 1) != 0 { + regs.uregs[15] = (func_addr & !1) as u32; // PC + regs.uregs[16] |= 0x20; // Set CPSR T bit (bit 5) + } else { + regs.uregs[15] = func_addr as u32; // PC + regs.uregs[16] &= !0x20; // Clear T bit + } + } + + set_regs(pid, regs)?; + if unsafe { libc::ptrace(libc::PTRACE_CONT, pid.as_raw(), 0, 0) } != 0 { + bail!( + "ptrace(PTRACE_CONT) failed: {}", + std::io::Error::last_os_error() + ); + } + + Ok(()) +} + +fn wait_remote_call(pid: Pid, return_addr: usize) -> Result { + wait_pid(pid, Signal::SIGSEGV)?; + let regs = get_regs(pid)?; + + #[cfg(target_arch = "x86_64")] + { + if regs.rip != (return_addr as u64) { + error!( + "Unexpected RIP after remote call: expected 0x{:x}, got 0x{:x}", + return_addr, regs.rip + ); + match nix::sys::ptrace::getsiginfo(pid) { + Ok(info) => { + error!("Signal info: {:?}", info); + } + Err(e) => { + error!("Failed to get signal info: {}", e); + } + } + bail!("Remote call did not reach expected function address"); + } + + Ok(regs.rax as usize) + } + #[cfg(target_arch = "x86")] + { + if regs.eip != (return_addr as u32) { + error!( + "Unexpected EIP after remote call: expected 0x{:x}, got 0x{:x}", + return_addr, regs.eip + ); + match nix::sys::ptrace::getsiginfo(pid) { + Ok(info) => { + error!("Signal info: {:?}", info); + } + Err(e) => { + error!("Failed to get signal info: {}", e); + } + } + bail!("Remote call did not reach expected function address"); + } + + Ok(regs.eax as usize) + } + + #[cfg(target_arch = "aarch64")] + { + if regs.pc != (return_addr as u64) { + error!( + "Unexpected PC after remote call: expected 0x{:x}, got 0x{:x}", + return_addr, regs.pc + ); + match nix::sys::ptrace::getsiginfo(pid) { + Ok(info) => { + error!("Signal info: {:?}", info); + } + Err(e) => { + error!("Failed to get signal info: {}", e); + } + } + bail!("Remote call did not reach expected function address"); + } + + Ok(regs.regs[0] as usize) + } + #[cfg(target_arch = "arm")] + { + if regs.uregs[15] != (return_addr as u32) { + error!( + "Unexpected PC after remote call: expected 0x{:x}, got 0x{:x}", + return_addr, regs.uregs[15] + ); + match nix::sys::ptrace::getsiginfo(pid) { + Ok(info) => { + error!("Signal info: {:?}", info); + } + Err(e) => { + error!("Failed to get signal info: {}", e); + } + } + bail!("Remote call did not reach expected function address"); + } + + Ok(regs.uregs[0] as usize) + } +} + +pub fn remote_call( + pid: Pid, + func_addr: usize, + return_addr: usize, + args: &[usize], +) -> Result { + debug!( + "Performing remote call to 0x{:x} with return address 0x{:x} and args {:?}", + func_addr, return_addr, args + ); + let original_regs = get_regs(pid).context("Failed to backup registers.")?; + let mut regs = original_regs; + + setup_remote_call(pid, &mut regs, func_addr, return_addr, args)?; + let ret = wait_remote_call(pid, return_addr)?; + + // restore original registers + set_regs(pid, &original_regs).context("Failed to restore registers.")?; + + Ok(ret) +} diff --git a/injector/src/utils.rs b/injector/src/utils.rs new file mode 100644 index 0000000..1549599 --- /dev/null +++ b/injector/src/utils.rs @@ -0,0 +1,192 @@ +use std::os::unix::ffi::OsStrExt; +use std::{ffi::CString, path::PathBuf}; + +use std::ffi::{c_void, CStr}; + +use anyhow::{anyhow, Context, Result}; +use log::{debug, error}; +use lsplt_rs::MapInfo; +use nix::{ + dir::{Dir, Type}, + fcntl::OFlag, + sys::stat::Mode, +}; + +// SELinux stuff +pub fn set_sockcreate_con(context: &str) -> Result<()> { + let context = CString::new(context).context("Invalid context string")?; + let context = context.as_bytes_with_nul(); + if let Err(e) = std::fs::write("/proc/thread-self/attr/sockcreate", context) { + error!("Failed to set sockcreate context: {}", e); + + let tid = unsafe { libc::gettid() as usize }; + std::fs::write(format!("/proc/{}/attr/sockcreate", tid), context) + .context("Failed to set sockcreate context via /proc/[tid]/attr/sockcreate")?; + } + + Ok(()) +} + +pub fn set_file_con(path: &PathBuf, context: &str) -> Result<()> { + const XATTR_NAME_SELINUX: &CStr = c"security.selinux"; + + let path = + CString::new(path.as_os_str().as_bytes()).context("Invalid file path for setxattr")?; + let context = CString::new(context).context("Invalid context string")?; + let size = context.as_bytes_with_nul().len(); + + let result = unsafe { + libc::setxattr( + path.as_ptr(), + XATTR_NAME_SELINUX.as_ptr(), + context.as_ptr() as *const c_void, + size, + 0, + ) + }; + + if result != 0 { + Err(anyhow!( + "Failed to set file context: {}", + std::io::Error::last_os_error() + )) + } else { + Ok(()) + } +} + +// Hook stuff +pub fn resolve_base_addr(info: &[MapInfo], lib_name: &str) -> Result { + for map in info { + if let Some(path) = &map.pathname { + if map.offset == 0 && path.as_str().ends_with(lib_name) { + debug!( + "Found library '{}' at base address: 0x{:x}", + lib_name, map.start + ); + return Ok(map.start); + } + } + } + Err(anyhow!("Library '{}' not found in process maps", lib_name)) +} + +pub fn resolve_return_addr(info: &[MapInfo], lib_name: &str) -> Result { + for map in info { + if let Some(path) = &map.pathname { + if (map.perms & libc::PROT_EXEC as u8) == 0 && path.as_str().ends_with(lib_name) { + let data_addr = map.start + map.offset; + debug!( + "Found data seg in library '{}' at address: 0x{:x}", + lib_name, data_addr + ); + return Ok(data_addr); + } + } + } + Err(anyhow!("Not found in library '{}'", lib_name)) +} + +pub fn resolve_func_addr( + local: &[MapInfo], + remote: &[MapInfo], + lib_name: &str, + name: &str, +) -> Result { + let lib = unsafe { + let lib = CString::new(lib_name).map_err(|_| anyhow!("Invalid library name"))?; + libc::dlopen(lib.as_ptr(), libc::RTLD_NOW) + }; + if lib.is_null() { + return Err(anyhow!( + "Failed to open library '{}': {}", + lib_name, + std::io::Error::last_os_error() + )); + } + + let symbol = unsafe { + let name = CString::new(name).map_err(|_| anyhow!("Invalid function name"))?; + libc::dlsym(lib, name.as_ptr()) + }; + if symbol.is_null() { + return Err(anyhow!( + "Failed to find symbol '{}' in library '{}': {}", + name, + lib_name, + std::io::Error::last_os_error() + )); + } + + unsafe { + libc::dlclose(lib); + } + + let local_addr = resolve_base_addr(local, lib_name) + .context(format!("failed to find local base for module {}", lib_name))?; + let remote_addr = resolve_base_addr(remote, lib_name).context(format!( + "failed to find remote base for module {}", + lib_name + ))?; + + let offset = (symbol as usize) + .checked_sub(local_addr) + .ok_or_else(|| anyhow!("Invalid symbol address"))?; + let remote_func_addr = remote_addr + .checked_add(offset) + .ok_or_else(|| anyhow!("Address overflow"))?; + + debug!( + "Resolved function '{}' address: 0x{:x}", + name, remote_func_addr + ); + Ok(remote_func_addr) +} + +pub fn find_process_by_name(target_name: &str) -> Result<(i32, PathBuf)> { + let mut proc_dir = Dir::open("/proc", OFlag::O_RDONLY | OFlag::O_DIRECTORY, Mode::empty())?; + + for entry_result in proc_dir.iter() { + let entry = match entry_result { + Ok(entry) => entry, + Err(_) => continue, + }; + + if entry.file_type() != Some(Type::Directory) { + continue; + } + + let file_name = entry.file_name(); + + let pid_str = file_name.to_str().unwrap(); + if !pid_str.chars().all(char::is_numeric) { + continue; + } + + let path = PathBuf::from("/proc").join(pid_str); + + let comm_path = path.join("exe"); + let target = match std::fs::read_link(comm_path) { + Ok(name) => name, + Err(_) => continue, + }; + let file_name = match target.file_name().and_then(std::ffi::OsStr::to_str) { + Some(name) => name, + None => continue, + }; + if file_name.trim() != target_name { + continue; + } + + let pid = pid_str.parse::().unwrap(); + debug!("Found target executable: {:?} (PID {})", target, pid); + + return Ok((pid, target)); + } + + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Process '{}' not found", target_name), + )) + .context("") +} diff --git a/src/keymaster/db.rs b/src/keymaster/db.rs index c070b2b..6a7e93c 100644 --- a/src/keymaster/db.rs +++ b/src/keymaster/db.rs @@ -1735,11 +1735,13 @@ impl KeymasterDb { AND state = ? AND key_type = ?;", params![domain.0 as u32, namespace, KeyLifeCycle::Live, key_type], - |row| row.get(0), + |row| row.get::<_, i32>(0), ) .context(err!("Failed to count number of keys.")) .no_gc() })?; + let num_keys = + usize::try_from(num_keys).context(err!("Failed to convert key count to usize."))?; Ok(num_keys) } diff --git a/src/keymaster/keymint_device.rs b/src/keymaster/keymint_device.rs index abf1afb..12eea14 100644 --- a/src/keymaster/keymint_device.rs +++ b/src/keymaster/keymint_device.rs @@ -412,7 +412,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result: InternalBeginResult = match result.rsp.unwrap() { PerformOpRsp::DeviceBegin(rsp) => rsp.ret, - _ => unreachable!("Unexpected response type") + _ => unreachable!("Unexpected response type"), }; let operation = crate::keymaster::keymint_operation::KeyMintOperation::new( @@ -517,7 +517,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceGenerateKey(rsp) => rsp.ret, - _ => unreachable!("Unexpected response type") + _ => unreachable!("Unexpected response type"), }; let resp = key_creation_result_to_aidl(result)?; @@ -576,7 +576,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceImportKey(rsp) => rsp.ret, - _ => unreachable!("Unexpected response type") + _ => unreachable!("Unexpected response type"), }; let resp = key_creation_result_to_aidl(result)?; @@ -739,7 +739,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceConvertStorageKeyToEphemeral(rsp) => rsp.ret, - _ => unreachable!("Unexpected response type") + _ => unreachable!("Unexpected response type"), }; Result::Ok(result) @@ -765,7 +765,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::DeviceGetKeyCharacteristics(rsp) => rsp.ret, - _ => unreachable!("Unexpected response type") + _ => unreachable!("Unexpected response type"), }; let result: Result, rsbinder::Status> = result.iter().map(|kc| { @@ -826,7 +826,7 @@ impl IKeyMintDevice for KeyMintWrapper { } let result = match result.rsp.unwrap() { PerformOpRsp::GetRootOfTrust(rsp) => rsp.ret, - _ => unreachable!("Unexpected response type") + _ => unreachable!("Unexpected response type"), }; Result::Ok(result) @@ -909,12 +909,12 @@ impl KeyMintWrapper { None }; let timestamp_token = timestamp_token.map(|tt| kmr_wire::secureclock::TimeStampToken { - challenge: tt.challenge, - timestamp: kmr_wire::secureclock::Timestamp { - milliseconds: tt.timestamp.milliSeconds, - }, - mac: tt.mac.clone(), - }); + challenge: tt.challenge, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: tt.timestamp.milliSeconds, + }, + mac: tt.mac.clone(), + }); let req = PerformOpReq::OperationUpdateAad(UpdateAadRequest { op_handle, @@ -954,12 +954,12 @@ impl KeyMintWrapper { None }; let timestamp_token = timestamp_token.map(|tt| kmr_wire::secureclock::TimeStampToken { - challenge: tt.challenge, - timestamp: kmr_wire::secureclock::Timestamp { - milliseconds: tt.timestamp.milliSeconds, - }, - mac: tt.mac.clone(), - }); + challenge: tt.challenge, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: tt.timestamp.milliSeconds, + }, + mac: tt.mac.clone(), + }); let req = PerformOpReq::OperationUpdate(UpdateRequest { op_handle, @@ -1001,12 +1001,12 @@ impl KeyMintWrapper { None }; let timestamp_token = timestamp_token.map(|tt| kmr_wire::secureclock::TimeStampToken { - challenge: tt.challenge, - timestamp: kmr_wire::secureclock::Timestamp { - milliseconds: tt.timestamp.milliSeconds, - }, - mac: tt.mac.clone(), - }); + challenge: tt.challenge, + timestamp: kmr_wire::secureclock::Timestamp { + milliseconds: tt.timestamp.milliSeconds, + }, + mac: tt.mac.clone(), + }); let input = input.map(|i| i.to_vec()); let signature = signature.map(|s| s.to_vec()); let confirmation_token = confirmation_token.map(|c| c.to_vec()); diff --git a/src/keymaster/keymint_operation.rs b/src/keymaster/keymint_operation.rs index 55fb5c6..b3bbfd4 100644 --- a/src/keymaster/keymint_operation.rs +++ b/src/keymaster/keymint_operation.rs @@ -64,7 +64,8 @@ impl IKeyMintOperation for KeyMintOperation { get_keymint_wrapper(self.security_level) .unwrap() .op_update(self.op_handle, input, authToken, timeStampToken) - .map_err(map_ks_error).map(|rsp: Vec| rsp.to_vec()) + .map_err(map_ks_error) + .map(|rsp: Vec| rsp.to_vec()) } fn r#finish( @@ -87,7 +88,8 @@ impl IKeyMintOperation for KeyMintOperation { timestampToken, confirmationToken, ) - .map_err(map_ks_error).map(|rsp: Vec| rsp.to_vec()) + .map_err(map_ks_error) + .map(|rsp: Vec| rsp.to_vec()) } fn r#abort(&self) -> rsbinder::status::Result<()> { diff --git a/src/keymaster/security_level.rs b/src/keymaster/security_level.rs index 9c0bf7a..c52a3cb 100644 --- a/src/keymaster/security_level.rs +++ b/src/keymaster/security_level.rs @@ -143,23 +143,23 @@ impl KeystoreSecurityLevel { // Add CREATION_DATETIME only if the backend version Keymint V1 (100) or newer. // if self.hw_info.version_number >= 100 { - result.push(KeyParameter { - tag: Tag::CREATION_DATETIME, - value: KeyParameterValue::DateTime( - creation_datetime - .duration_since(SystemTime::UNIX_EPOCH) - .context(err!( - "KeystoreSecurityLevel::add_required_parameters: \ + result.push(KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KeyParameterValue::DateTime( + creation_datetime + .duration_since(SystemTime::UNIX_EPOCH) + .context(err!( + "KeystoreSecurityLevel::add_required_parameters: \ Failed to get epoch time." - ))? - .as_millis() - .try_into() - .context(err!( - "KeystoreSecurityLevel::add_required_parameters: \ + ))? + .as_millis() + .try_into() + .context(err!( + "KeystoreSecurityLevel::add_required_parameters: \ Failed to convert epoch time." - ))?, - ), - }); + ))?, + ), + }); // } // If there is an attestation challenge we need to get an application id. @@ -1005,8 +1005,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { forced: bool, ) -> Result { let _wp = self.watch("IKeystoreSecurityLevel::createOperation"); - self - .create_operation(None, key, operation_parameters, forced) + self.create_operation(None, key, operation_parameters, forced) .map_err(into_logged_binder) } @@ -1097,8 +1096,7 @@ impl IOhMySecurityLevel for KeystoreSecurityLevel { forced: bool, ) -> Result { let _wp = self.watch("IOhMySecurityLevel::createOperation"); - self - .create_operation(ctx, key, operation_parameters, forced) + self.create_operation(ctx, key, operation_parameters, forced) .map_err(into_logged_binder) } diff --git a/src/keymaster/super_key.rs b/src/keymaster/super_key.rs index 9f87207..9b9c0a7 100644 --- a/src/keymaster/super_key.rs +++ b/src/keymaster/super_key.rs @@ -364,9 +364,10 @@ impl SuperKeyManager { } pub fn level_accessible(&self, boot_level: i32) -> bool { - self.data.boot_level_key_cache.as_ref().is_some_and(|c| { - c.lock().unwrap().level_accessible(boot_level as usize) - }) + self.data + .boot_level_key_cache + .as_ref() + .is_some_and(|c| c.lock().unwrap().level_accessible(boot_level as usize)) } pub fn forget_all_keys_for_user(&mut self, user: UserId) { diff --git a/src/plat/property_watcher.rs b/src/plat/property_watcher.rs index 7ebf2d5..6ff9a7d 100644 --- a/src/plat/property_watcher.rs +++ b/src/plat/property_watcher.rs @@ -1,6 +1,3 @@ -use log::info; -use rsproperties::PropertyConfig; - #[cfg(not(target_os = "android"))] use rsproperties_service; diff --git a/src/plat/utils.rs b/src/plat/utils.rs index bb0c375..36cb82c 100644 --- a/src/plat/utils.rs +++ b/src/plat/utils.rs @@ -109,7 +109,7 @@ pub fn get_aaid(uid: u32) -> anyhow::Result> { // unsafe { // libc::seteuid(1017); // KEYSTORE_UID // } - + // unsafe { // libc::seteuid(current_euid); // } diff --git a/ta/src/cert.rs b/ta/src/cert.rs index 7148f0a..b566d60 100644 --- a/ta/src/cert.rs +++ b/ta/src/cert.rs @@ -1375,7 +1375,7 @@ struct RootOfTrust<'a> { } impl<'a> From<&'a keymint::BootInfo> for RootOfTrust<'a> { - fn from(info: &keymint::BootInfo) -> RootOfTrust { + fn from(info: &keymint::BootInfo) -> RootOfTrust<'_> { let verified_boot_key: &[u8] = if info.verified_boot_key.is_empty() { // If an empty verified boot key was passed by the boot loader, set the verified boot // key in the attestation to all zeroes. diff --git a/ta/src/clock.rs b/ta/src/clock.rs index 906be9b..b8945d5 100644 --- a/ta/src/clock.rs +++ b/ta/src/clock.rs @@ -17,7 +17,6 @@ use core::mem::size_of; use kmr_common::{km_err, vec_try_with_capacity, Error}; use kmr_wire::secureclock::{TimeStampToken, TIME_STAMP_MAC_LABEL}; -use Vec; impl crate::KeyMintTa { pub(crate) fn generate_timestamp(&self, challenge: i64) -> Result { diff --git a/ta/src/keys.rs b/ta/src/keys.rs index 61ccbb7..adc8e2b 100644 --- a/ta/src/keys.rs +++ b/ta/src/keys.rs @@ -399,7 +399,10 @@ impl crate::KeyMintTa { key_material: KeyMaterial, purpose: keyblob::SlotPurpose, ) -> Result { - debug!("finish_keyblob_creation: params {:?} attestKey {:?}", params, attestation_key); + debug!( + "finish_keyblob_creation: params {:?} attestKey {:?}", + params, attestation_key + ); debug!("key characteristics: {:?}", chars); debug!("key material: {:?}", key_material); debug!("keyblob purpose: {:?}", purpose); diff --git a/ta/src/lib.rs b/ta/src/lib.rs index dd25e43..08e1098 100644 --- a/ta/src/lib.rs +++ b/ta/src/lib.rs @@ -196,7 +196,7 @@ pub fn split_rsp(mut rsp_data: &[u8], max_size: usize) -> Result>, E // Need to allocate one byte for the more_msg_signal. let allowed_msg_length = max_size - 1; let mut num_of_splits = rsp_data.len() / allowed_msg_length; - if rsp_data.len() % allowed_msg_length > 0 { + if !rsp_data.len().is_multiple_of(allowed_msg_length) { num_of_splits += 1; } let mut split_rsp = vec_try_with_capacity!(num_of_splits)?; From 8b4864e95191354a26b02389717393e8fb6611df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:31:45 +0000 Subject: [PATCH 46/46] chore(deps): update syscalls requirement from 0.7.0 to 0.8.1 Updates the requirements on [syscalls](https://github.com/jasonwhite/syscalls) to permit the latest version. - [Changelog](https://github.com/jasonwhite/syscalls/blob/main/CHANGELOG.md) - [Commits](https://github.com/jasonwhite/syscalls/compare/0.7.0...0.8.1) --- updated-dependencies: - dependency-name: syscalls dependency-version: 0.8.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 99ff138..8574e8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ path = "src/main.rs" [dependencies] jni = "0.21.1" -syscalls = "0.7.0" +syscalls = "0.8.1" kmr-derive = { path = "derive" } kmr-common = { path = "common" } kmr-crypto-boring = { path = "boringssl" }